Import libmonet update from internal repo
Bug: 266731167 Test: atest com.android.systemui.monet, 380 pass. Original test count was 13. Library has 90%+ coverage overall and is maintaining 90%+ coverage as CLs are added, per stats from internal source repository. Change-Id: I922b352facb5da89d77583602381291ac571842a
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
Subpackages, i.e. folders, in this directory have their source of truth in the internal Google
|
||||
source code repository.
|
||||
|
||||
The code is manually copied because:
|
||||
|
||||
- Alternatives are automatically syncing Material Color Utilities from the internal Google source
|
||||
repository, or GitHub.
|
||||
- Having Android's core automatically taking updates from code that changes regularly, and can
|
||||
easily affect design, is undesirable.
|
||||
|
||||
To update the code:
|
||||
|
||||
1. Copy Material Color Utilities (libmonet internally) from the internal Google source repository to
|
||||
this directory.
|
||||
2. Replace package definitions. Use Find and Replace in directory, replace
|
||||
com.google.ux.material.libmonet with com.android.systemui.monet.
|
||||
3. Remove @CanIgnoreReturnValue and @CheckReturnValue, both usage and imports. Currently only in
|
||||
Scheme.java. Chose removal because only Android /external libraries depend on the library with
|
||||
these annotations.
|
||||
4. Copy tests for Material Color Utilities to
|
||||
frameworks/base/packages/SystemUI/tests/src/com/android/systemui/monet/
|
||||
5. Remove HctRoundTripTest (~3 minutes to run) and QuantizerCelebiTest (depends on internal source
|
||||
repository code for handling images)
|
||||
6. Reformat regular and test code to Android bracing/spacing style.
|
||||
In Android Studio, right click on the package, and choose Reformat Code.
|
||||
7. Change tests to subclass SysuiTestCase and use an annotation such as @SmallTest / @MediumTest.
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.blend;
|
||||
|
||||
import com.android.systemui.monet.hct.Cam16;
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.utils.ColorUtils;
|
||||
import com.android.systemui.monet.utils.MathUtils;
|
||||
|
||||
/** Functions for blending in HCT and CAM16. */
|
||||
public class Blend {
|
||||
private Blend() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Blend the design color's HCT hue towards the key color's HCT hue, in a way that leaves the
|
||||
* original color recognizable and recognizably shifted towards the key color.
|
||||
*
|
||||
* @param designColor ARGB representation of an arbitrary color.
|
||||
* @param sourceColor ARGB representation of the main theme color.
|
||||
* @return The design color with a hue shifted towards the system's color, a slightly
|
||||
* warmer/cooler variant of the design color's hue.
|
||||
*/
|
||||
public static int harmonize(int designColor, int sourceColor) {
|
||||
Hct fromHct = Hct.fromInt(designColor);
|
||||
Hct toHct = Hct.fromInt(sourceColor);
|
||||
double differenceDegrees = MathUtils.differenceDegrees(fromHct.getHue(), toHct.getHue());
|
||||
double rotationDegrees = Math.min(differenceDegrees * 0.5, 15.0);
|
||||
double outputHue =
|
||||
MathUtils.sanitizeDegreesDouble(
|
||||
fromHct.getHue()
|
||||
+ rotationDegrees * MathUtils.rotationDirection(fromHct.getHue(),
|
||||
toHct.getHue()));
|
||||
return Hct.from(outputHue, fromHct.getChroma(), fromHct.getTone()).toInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Blends hue from one color into another. The chroma and tone of the original color are
|
||||
* maintained.
|
||||
*
|
||||
* @param from ARGB representation of color
|
||||
* @param to ARGB representation of color
|
||||
* @param amount how much blending to perform; 0.0 >= and <= 1.0
|
||||
* @return from, with a hue blended towards to. Chroma and tone are constant.
|
||||
*/
|
||||
public static int hctHue(int from, int to, double amount) {
|
||||
int ucs = cam16Ucs(from, to, amount);
|
||||
Cam16 ucsCam = Cam16.fromInt(ucs);
|
||||
Cam16 fromCam = Cam16.fromInt(from);
|
||||
Hct blended = Hct.from(ucsCam.getHue(), fromCam.getChroma(),
|
||||
ColorUtils.lstarFromArgb(from));
|
||||
return blended.toInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Blend in CAM16-UCS space.
|
||||
*
|
||||
* @param from ARGB representation of color
|
||||
* @param to ARGB representation of color
|
||||
* @param amount how much blending to perform; 0.0 >= and <= 1.0
|
||||
* @return from, blended towards to. Hue, chroma, and tone will change.
|
||||
*/
|
||||
public static int cam16Ucs(int from, int to, double amount) {
|
||||
Cam16 fromCam = Cam16.fromInt(from);
|
||||
Cam16 toCam = Cam16.fromInt(to);
|
||||
double fromJ = fromCam.getJstar();
|
||||
double fromA = fromCam.getAstar();
|
||||
double fromB = fromCam.getBstar();
|
||||
double toJ = toCam.getJstar();
|
||||
double toA = toCam.getAstar();
|
||||
double toB = toCam.getBstar();
|
||||
double jstar = fromJ + (toJ - fromJ) * amount;
|
||||
double astar = fromA + (toA - fromA) * amount;
|
||||
double bstar = fromB + (toB - fromB) * amount;
|
||||
return Cam16.fromUcs(jstar, astar, bstar).toInt();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.contrast;
|
||||
|
||||
|
||||
import static java.lang.Math.max;
|
||||
|
||||
import com.android.systemui.monet.utils.ColorUtils;
|
||||
|
||||
/**
|
||||
* Color science for contrast utilities.
|
||||
*
|
||||
* <p>Utility methods for calculating contrast given two colors, or calculating a color given one
|
||||
* color and a contrast ratio.
|
||||
*
|
||||
* <p>Contrast ratio is calculated using XYZ's Y. When linearized to match human perception, Y
|
||||
* becomes HCT's tone and L*a*b*'s' L*.
|
||||
*/
|
||||
public final class Contrast {
|
||||
// The minimum contrast ratio of two colors.
|
||||
// Contrast ratio equation = lighter + 5 / darker + 5, if lighter == darker, ratio == 1.
|
||||
public static final double RATIO_MIN = 1.0;
|
||||
|
||||
// The maximum contrast ratio of two colors.
|
||||
// Contrast ratio equation = lighter + 5 / darker + 5. Lighter and darker scale from 0 to 100.
|
||||
// If lighter == 100, darker = 0, ratio == 21.
|
||||
public static final double RATIO_MAX = 21.0;
|
||||
public static final double RATIO_30 = 3.0;
|
||||
public static final double RATIO_45 = 4.5;
|
||||
public static final double RATIO_70 = 7.0;
|
||||
|
||||
// Given a color and a contrast ratio to reach, the luminance of a color that reaches that ratio
|
||||
// with the color can be calculated. However, that luminance may not contrast as desired, i.e.
|
||||
// the contrast ratio of the input color and the returned luminance may not reach the contrast
|
||||
// ratio asked for.
|
||||
//
|
||||
// When the desired contrast ratio and the result contrast ratio differ by more than this
|
||||
// amount, an error value should be returned, or the method should be documented as 'unsafe',
|
||||
// meaning, it will return a valid luminance but that luminance may not meet the requested
|
||||
// contrast ratio.
|
||||
//
|
||||
// 0.04 selected because it ensures the resulting ratio rounds to the same tenth.
|
||||
private static final double CONTRAST_RATIO_EPSILON = 0.04;
|
||||
|
||||
// Color spaces that measure luminance, such as Y in XYZ, L* in L*a*b*, or T in HCT, are
|
||||
// known as perceptually accurate color spaces.
|
||||
//
|
||||
// To be displayed, they must gamut map to a "display space", one that has a defined limit on
|
||||
// the number of colors. Display spaces include sRGB, more commonly understood as RGB/HSL/HSV.
|
||||
//
|
||||
// Gamut mapping is undefined and not defined by the color space. Any gamut mapping algorithm
|
||||
// must choose how to sacrifice accuracy in hue, saturation, and/or lightness.
|
||||
//
|
||||
// A principled solution is to maintain lightness, thus maintaining contrast/a11y, maintain hue,
|
||||
// thus maintaining aesthetic intent, and reduce chroma until the color is in gamut.
|
||||
//
|
||||
// HCT chooses this solution, but, that doesn't mean it will _exactly_ matched desired
|
||||
// lightness, if only because RGB is quantized: RGB is expressed as a set of integers: there may
|
||||
// be an RGB color with, for example, 47.892 lightness, but not 47.891.
|
||||
//
|
||||
// To allow for this inherent incompatibility between perceptually accurate color spaces and
|
||||
// display color spaces, methods that take a contrast ratio and luminance, and return a
|
||||
// luminance that reaches that contrast ratio for the input luminance, purposefully
|
||||
// darken/lighten their result such that the desired contrast ratio will be reached even if
|
||||
// inaccuracy is introduced.
|
||||
//
|
||||
// 0.4 is generous, ex. HCT requires much less delta. It was chosen because it provides a rough
|
||||
// guarantee that as long as a percetual color space gamut maps lightness such that the
|
||||
// resulting lightness rounds to the same as the requested, the desired contrast ratio will be
|
||||
// reached.
|
||||
private static final double LUMINANCE_GAMUT_MAP_TOLERANCE = 0.4;
|
||||
|
||||
private Contrast() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Contrast ratio is a measure of legibility, its used to compare the lightness of two colors.
|
||||
* This method is used commonly in industry due to its use by WCAG.
|
||||
*
|
||||
* <p>To compare lightness, the colors are expressed in the XYZ color space, where Y is
|
||||
* lightness,
|
||||
* also known as relative luminance.
|
||||
*
|
||||
* <p>The equation is ratio = lighter Y + 5 / darker Y + 5.
|
||||
*/
|
||||
public static double ratioOfYs(double y1, double y2) {
|
||||
final double lighter = max(y1, y2);
|
||||
final double darker = (lighter == y2) ? y1 : y2;
|
||||
return (lighter + 5.0) / (darker + 5.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Contrast ratio of two tones. T in HCT, L* in L*a*b*. Also known as luminance or perpectual
|
||||
* luminance.
|
||||
*
|
||||
* <p>Contrast ratio is defined using Y in XYZ, relative luminance. However, relative luminance
|
||||
* is linear to number of photons, not to perception of lightness. Perceptual luminance, L* in
|
||||
* L*a*b*, T in HCT, is. Designers prefer color spaces with perceptual luminance since they're
|
||||
* accurate to the eye.
|
||||
*
|
||||
* <p>Y and L* are pure functions of each other, so it possible to use perceptually accurate
|
||||
* color spaces, and measure contrast, and measure contrast in a much more understandable way:
|
||||
* instead of a ratio, a linear difference. This allows a designer to determine what they need
|
||||
* to adjust a color's lightness to in order to reach their desired contrast, instead of
|
||||
* guessing & checking with hex codes.
|
||||
*/
|
||||
public static double ratioOfTones(double t1, double t2) {
|
||||
return ratioOfYs(ColorUtils.yFromLstar(t1), ColorUtils.yFromLstar(t2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns T in HCT, L* in L*a*b* >= tone parameter that ensures ratio with input T/L*. Returns
|
||||
* -1 if ratio cannot be achieved.
|
||||
*
|
||||
* @param tone Tone return value must contrast with.
|
||||
* @param ratio Desired contrast ratio of return value and tone parameter.
|
||||
*/
|
||||
public static double lighter(double tone, double ratio) {
|
||||
if (tone < 0.0 || tone > 100.0) {
|
||||
return -1.0;
|
||||
}
|
||||
// Invert the contrast ratio equation to determine lighter Y given a ratio and darker Y.
|
||||
final double darkY = ColorUtils.yFromLstar(tone);
|
||||
final double lightY = ratio * (darkY + 5.0) - 5.0;
|
||||
if (lightY < 0.0 || lightY > 100.0) {
|
||||
return -1.0;
|
||||
}
|
||||
final double realContrast = ratioOfYs(lightY, darkY);
|
||||
final double delta = Math.abs(realContrast - ratio);
|
||||
if (realContrast < ratio && delta > CONTRAST_RATIO_EPSILON) {
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
final double returnValue = ColorUtils.lstarFromY(lightY) + LUMINANCE_GAMUT_MAP_TOLERANCE;
|
||||
// NOMUTANTS--important validation step; functions it is calling may change implementation.
|
||||
if (returnValue < 0 || returnValue > 100) {
|
||||
return -1.0;
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tone >= tone parameter that ensures ratio. 100 if ratio cannot be achieved.
|
||||
*
|
||||
* <p>This method is unsafe because the returned value is guaranteed to be in bounds, but, the
|
||||
* in bounds return value may not reach the desired ratio.
|
||||
*
|
||||
* @param tone Tone return value must contrast with.
|
||||
* @param ratio Desired contrast ratio of return value and tone parameter.
|
||||
*/
|
||||
public static double lighterUnsafe(double tone, double ratio) {
|
||||
double lighterSafe = lighter(tone, ratio);
|
||||
return lighterSafe < 0.0 ? 100.0 : lighterSafe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns T in HCT, L* in L*a*b* <= tone parameter that ensures ratio with input T/L*. Returns
|
||||
* -1 if ratio cannot be achieved.
|
||||
*
|
||||
* @param tone Tone return value must contrast with.
|
||||
* @param ratio Desired contrast ratio of return value and tone parameter.
|
||||
*/
|
||||
public static double darker(double tone, double ratio) {
|
||||
if (tone < 0.0 || tone > 100.0) {
|
||||
return -1.0;
|
||||
}
|
||||
// Invert the contrast ratio equation to determine darker Y given a ratio and lighter Y.
|
||||
final double lightY = ColorUtils.yFromLstar(tone);
|
||||
final double darkY = ((lightY + 5.0) / ratio) - 5.0;
|
||||
if (darkY < 0.0 || darkY > 100.0) {
|
||||
return -1.0;
|
||||
}
|
||||
final double realContrast = ratioOfYs(lightY, darkY);
|
||||
final double delta = Math.abs(realContrast - ratio);
|
||||
if (realContrast < ratio && delta > CONTRAST_RATIO_EPSILON) {
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
// For information on 0.4 constant, see comment in lighter(tone, ratio).
|
||||
final double returnValue = ColorUtils.lstarFromY(darkY) - LUMINANCE_GAMUT_MAP_TOLERANCE;
|
||||
// NOMUTANTS--important validation step; functions it is calling may change implementation.
|
||||
if (returnValue < 0 || returnValue > 100) {
|
||||
return -1.0;
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tone <= tone parameter that ensures ratio. 0 if ratio cannot be achieved.
|
||||
*
|
||||
* <p>This method is unsafe because the returned value is guaranteed to be in bounds, but, the
|
||||
* in bounds return value may not reach the desired ratio.
|
||||
*
|
||||
* @param tone Tone return value must contrast with.
|
||||
* @param ratio Desired contrast ratio of return value and tone parameter.
|
||||
*/
|
||||
public static double darkerUnsafe(double tone, double ratio) {
|
||||
double darkerSafe = darker(tone, ratio);
|
||||
return max(0.0, darkerSafe);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.dislike;
|
||||
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
|
||||
/**
|
||||
* Check and/or fix universally disliked colors.
|
||||
*
|
||||
* <p>Color science studies of color preference indicate universal distaste for dark yellow-greens,
|
||||
* and also show this is correlated to distate for biological waste and rotting food.
|
||||
*
|
||||
* <p>See Palmer and Schloss, 2010 or Schloss and Palmer's Chapter 21 in Handbook of Color
|
||||
* Psychology (2015).
|
||||
*/
|
||||
public final class DislikeAnalyzer {
|
||||
|
||||
private DislikeAnalyzer() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if color is disliked.
|
||||
*
|
||||
* <p>Disliked is defined as a dark yellow-green that is not neutral.
|
||||
*/
|
||||
public static boolean isDisliked(Hct hct) {
|
||||
final boolean huePasses = Math.round(hct.getHue()) >= 90.0 && Math.round(hct.getHue())
|
||||
<= 111.0;
|
||||
final boolean chromaPasses = Math.round(hct.getChroma()) > 16.0;
|
||||
final boolean tonePasses = Math.round(hct.getTone()) < 70.0;
|
||||
|
||||
return huePasses && chromaPasses && tonePasses;
|
||||
}
|
||||
|
||||
/** If color is disliked, lighten it to make it likable. */
|
||||
public static Hct fixIfDisliked(Hct hct) {
|
||||
if (isDisliked(hct)) {
|
||||
return Hct.from(hct.getHue(), hct.getChroma(), 70.0);
|
||||
}
|
||||
|
||||
return hct;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,646 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.dynamiccolor;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
import com.android.systemui.monet.contrast.Contrast;
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.palettes.TonalPalette;
|
||||
import com.android.systemui.monet.scheme.DynamicScheme;
|
||||
import com.android.systemui.monet.utils.MathUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* A color that adjusts itself based on UI state, represented by DynamicScheme.
|
||||
*
|
||||
* <p>This color automatically adjusts to accommodate a desired contrast level, or other adjustments
|
||||
* such as differing in light mode versus dark mode, or what the theme is, or what the color that
|
||||
* produced the theme is, etc.
|
||||
*
|
||||
* <p>Colors without backgrounds do not change tone when contrast changes. Colors with backgrounds
|
||||
* become closer to their background as contrast lowers, and further when contrast increases.
|
||||
*
|
||||
* <p>Prefer the static constructors. They provide a much more simple interface, such as requiring
|
||||
* just a hexcode, or just a hexcode and a background.
|
||||
*
|
||||
* <p>Ultimately, each component necessary for calculating a color, adjusting it for a desired
|
||||
* contrast level, and ensuring it has a certain lightness/tone difference from another color, is
|
||||
* provided by a function that takes a DynamicScheme and returns a value. This ensures ultimate
|
||||
* flexibility, any desired behavior of a color for any design system, but it usually unnecessary.
|
||||
* See the default constructor for more information.
|
||||
*/
|
||||
// Prevent lint for Function.apply not being available on Android before API level 14 (4.0.1).
|
||||
// "AndroidJdkLibsChecker" for Function, "NewApi" for Function.apply().
|
||||
// A java_library Bazel rule with an Android constraint cannot skip these warnings without this
|
||||
// annotation; another solution would be to create an android_library rule and supply
|
||||
// AndroidManifest with an SDK set higher than 14.
|
||||
@SuppressWarnings({"AndroidJdkLibsChecker", "NewApi"})
|
||||
public final class DynamicColor {
|
||||
public final Function<DynamicScheme, Double> hue;
|
||||
public final Function<DynamicScheme, Double> chroma;
|
||||
public final Function<DynamicScheme, Double> tone;
|
||||
public final Function<DynamicScheme, Double> opacity;
|
||||
|
||||
public final Function<DynamicScheme, DynamicColor> background;
|
||||
public final Function<DynamicScheme, Double> toneMinContrast;
|
||||
public final Function<DynamicScheme, Double> toneMaxContrast;
|
||||
public final Function<DynamicScheme, ToneDeltaConstraint> toneDeltaConstraint;
|
||||
|
||||
private final HashMap<DynamicScheme, Hct> hctCache = new HashMap<>();
|
||||
|
||||
/**
|
||||
* The base constructor for DynamicColor.
|
||||
*
|
||||
* <p>Functional arguments allow overriding without risks that come with subclasses. _Strongly_
|
||||
* prefer using one of the static convenience constructors. This class is arguably too flexible
|
||||
* to
|
||||
* ensure it can support any scenario.
|
||||
*
|
||||
* <p>For example, the default behavior of adjust tone at max contrast to be at a 7.0 ratio
|
||||
* with
|
||||
* its background is principled and matches a11y guidance. That does not mean it's the desired
|
||||
* approach for _every_ design system, and every color pairing, always, in every case.
|
||||
*
|
||||
* @param hue given DynamicScheme, return the hue in HCT of the output color.
|
||||
* @param chroma given DynamicScheme, return chroma in HCT of the output color.
|
||||
* @param tone given DynamicScheme, return tone in HCT of the output color.
|
||||
* @param background given DynamicScheme, return the DynamicColor that is the
|
||||
* background of this
|
||||
* DynamicColor. When this is provided, automated adjustments to
|
||||
* lower and raise contrast are
|
||||
* made.
|
||||
* @param toneMinContrast given DynamicScheme, return tone in HCT/L* in L*a*b* this color
|
||||
* should
|
||||
* be at minimum contrast. See toneMinContrastDefault for the default
|
||||
* behavior, and strongly
|
||||
* consider using it unless you have strong opinions on a11y. The
|
||||
* static constructors use it.
|
||||
* @param toneMaxContrast given DynamicScheme, return tone in HCT/L* in L*a*b* this color
|
||||
* should
|
||||
* be at maximum contrast. See toneMaxContrastDefault for the default
|
||||
* behavior, and strongly
|
||||
* consider using it unless you have strong opinions on a11y. The
|
||||
* static constructors use it.
|
||||
* @param toneDeltaConstraint given DynamicScheme, return a ToneDeltaConstraint instance that
|
||||
* describes a requirement that this DynamicColor must always have
|
||||
* some difference in tone/L*
|
||||
* from another DynamicColor.<br>
|
||||
* Unlikely to be useful unless a design system has some distortions
|
||||
* where colors that don't
|
||||
* have a background/foreground relationship must have _some_
|
||||
* difference in tone, yet, not
|
||||
* enough difference to create meaningful contrast.
|
||||
*/
|
||||
public DynamicColor(
|
||||
Function<DynamicScheme, Double> hue,
|
||||
Function<DynamicScheme, Double> chroma,
|
||||
Function<DynamicScheme, Double> tone,
|
||||
Function<DynamicScheme, Double> opacity,
|
||||
Function<DynamicScheme, DynamicColor> background,
|
||||
Function<DynamicScheme, Double> toneMinContrast,
|
||||
Function<DynamicScheme, Double> toneMaxContrast,
|
||||
Function<DynamicScheme, ToneDeltaConstraint> toneDeltaConstraint) {
|
||||
this.hue = hue;
|
||||
this.chroma = chroma;
|
||||
this.tone = tone;
|
||||
this.opacity = opacity;
|
||||
this.background = background;
|
||||
this.toneMinContrast = toneMinContrast;
|
||||
this.toneMaxContrast = toneMaxContrast;
|
||||
this.toneDeltaConstraint = toneDeltaConstraint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a DynamicColor from a hex code.
|
||||
*
|
||||
* <p>Result has no background; thus no support for increasing/decreasing contrast for a11y.
|
||||
*/
|
||||
public static DynamicColor fromArgb(int argb) {
|
||||
final Hct hct = Hct.fromInt(argb);
|
||||
final TonalPalette palette = TonalPalette.fromInt(argb);
|
||||
return DynamicColor.fromPalette((s) -> palette, (s) -> hct.getTone());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a DynamicColor from just a hex code.
|
||||
*
|
||||
* <p>Result has no background; thus cannot support increasing/decreasing contrast for a11y.
|
||||
*
|
||||
* @param argb A hex code.
|
||||
* @param tone Function that provides a tone given DynamicScheme. Useful for adjusting for dark
|
||||
* vs. light mode.
|
||||
*/
|
||||
public static DynamicColor fromArgb(int argb, Function<DynamicScheme, Double> tone) {
|
||||
return DynamicColor.fromPalette((s) -> TonalPalette.fromInt(argb), tone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a DynamicColor.
|
||||
*
|
||||
* <p>If you don't understand HCT fully, or your design team doesn't, but wants support for
|
||||
* automated contrast adjustment, this method is _extremely_ useful: you can take a standard
|
||||
* design system expressed as hex codes, create DynamicColors corresponding to each color, and
|
||||
* then wire up backgrounds.
|
||||
*
|
||||
* <p>If the design system uses the same hex code on multiple backgrounds, define that in
|
||||
* multiple
|
||||
* DynamicColors so that the background is accurate for each one. If you define a DynamicColor
|
||||
* with one background, and actually use it on another, DynamicColor can't guarantee contrast.
|
||||
* For
|
||||
* example, if you use a color on both black and white, increasing the contrast on one
|
||||
* necessarily
|
||||
* decreases contrast of the other.
|
||||
*
|
||||
* @param argb A hex code.
|
||||
* @param tone Function that provides a tone given DynamicScheme. (useful for dark vs.
|
||||
* light mode)
|
||||
* @param background Function that provides background DynamicColor given DynamicScheme. Useful
|
||||
* for contrast, given a background, colors can adjust to increase/decrease
|
||||
* contrast.
|
||||
*/
|
||||
public static DynamicColor fromArgb(
|
||||
int argb,
|
||||
Function<DynamicScheme, Double> tone,
|
||||
Function<DynamicScheme, DynamicColor> background) {
|
||||
return DynamicColor.fromPalette((s) -> TonalPalette.fromInt(argb), tone, background);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a DynamicColor from:
|
||||
*
|
||||
* @param argb A hex code.
|
||||
* @param tone Function that provides a tone given DynamicScheme. (useful for
|
||||
* dark vs. light mode)
|
||||
* @param background Function that provides background DynamicColor given
|
||||
* DynamicScheme. Useful
|
||||
* for contrast, given a background, colors can adjust to
|
||||
* increase/decrease contrast.
|
||||
* @param toneDeltaConstraint Function that provides a ToneDeltaConstraint given DynamicScheme.
|
||||
* Useful for ensuring lightness difference between colors that don't
|
||||
* _require_ contrast or
|
||||
* have a formal background/foreground relationship.
|
||||
*/
|
||||
public static DynamicColor fromArgb(
|
||||
int argb,
|
||||
Function<DynamicScheme, Double> tone,
|
||||
Function<DynamicScheme, DynamicColor> background,
|
||||
Function<DynamicScheme, ToneDeltaConstraint> toneDeltaConstraint) {
|
||||
return DynamicColor.fromPalette(
|
||||
(s) -> TonalPalette.fromInt(argb), tone, background, toneDeltaConstraint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a DynamicColor.
|
||||
*
|
||||
* @param palette Function that provides a TonalPalette given DynamicScheme. A TonalPalette is
|
||||
* defined by a hue and chroma, so this replaces the need to specify hue/chroma.
|
||||
* By providing
|
||||
* a tonal palette, when contrast adjustments are made, intended chroma can be
|
||||
* preserved. For
|
||||
* example, at T/L* 90, there is a significant limit to the amount of chroma.
|
||||
* There is no
|
||||
* colorful red, a red that light is pink. By preserving the _intended_ chroma if
|
||||
* lightness
|
||||
* lowers for contrast adjustments, the intended chroma is restored.
|
||||
* @param tone Function that provides a tone given DynamicScheme. (useful for dark vs. light
|
||||
* mode)
|
||||
*/
|
||||
public static DynamicColor fromPalette(
|
||||
Function<DynamicScheme, TonalPalette> palette, Function<DynamicScheme, Double> tone) {
|
||||
return fromPalette(palette, tone, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a DynamicColor.
|
||||
*
|
||||
* @param palette Function that provides a TonalPalette given DynamicScheme. A TonalPalette
|
||||
* is
|
||||
* defined by a hue and chroma, so this replaces the need to specify
|
||||
* hue/chroma. By providing
|
||||
* a tonal palette, when contrast adjustments are made, intended chroma can be
|
||||
* preserved. For
|
||||
* example, at T/L* 90, there is a significant limit to the amount of chroma.
|
||||
* There is no
|
||||
* colorful red, a red that light is pink. By preserving the _intended_ chroma
|
||||
* if lightness
|
||||
* lowers for contrast adjustments, the intended chroma is restored.
|
||||
* @param tone Function that provides a tone given DynamicScheme. (useful for dark vs.
|
||||
* light mode)
|
||||
* @param background Function that provides background DynamicColor given DynamicScheme. Useful
|
||||
* for contrast, given a background, colors can adjust to increase/decrease
|
||||
* contrast.
|
||||
*/
|
||||
public static DynamicColor fromPalette(
|
||||
Function<DynamicScheme, TonalPalette> palette,
|
||||
Function<DynamicScheme, Double> tone,
|
||||
Function<DynamicScheme, DynamicColor> background) {
|
||||
return fromPalette(palette, tone, background, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a DynamicColor.
|
||||
*
|
||||
* @param palette Function that provides a TonalPalette given DynamicScheme. A
|
||||
* TonalPalette is
|
||||
* defined by a hue and chroma, so this replaces the need to specify
|
||||
* hue/chroma. By providing
|
||||
* a tonal palette, when contrast adjustments are made, intended
|
||||
* chroma can be preserved. For
|
||||
* example, at T/L* 90, there is a significant limit to the amount of
|
||||
* chroma. There is no
|
||||
* colorful red, a red that light is pink. By preserving the
|
||||
* _intended_ chroma if lightness
|
||||
* lowers for contrast adjustments, the intended chroma is restored.
|
||||
* @param tone Function that provides a tone given DynamicScheme. (useful for
|
||||
* dark vs. light mode)
|
||||
* @param background Function that provides background DynamicColor given
|
||||
* DynamicScheme. Useful
|
||||
* for contrast, given a background, colors can adjust to
|
||||
* increase/decrease contrast.
|
||||
* @param toneDeltaConstraint Function that provides a ToneDeltaConstraint given DynamicScheme.
|
||||
* Useful for ensuring lightness difference between colors that don't
|
||||
* _require_ contrast or
|
||||
* have a formal background/foreground relationship.
|
||||
*/
|
||||
public static DynamicColor fromPalette(
|
||||
Function<DynamicScheme, TonalPalette> palette,
|
||||
Function<DynamicScheme, Double> tone,
|
||||
Function<DynamicScheme, DynamicColor> background,
|
||||
Function<DynamicScheme, ToneDeltaConstraint> toneDeltaConstraint) {
|
||||
return new DynamicColor(
|
||||
scheme -> palette.apply(scheme).getHue(),
|
||||
scheme -> palette.apply(scheme).getChroma(),
|
||||
tone,
|
||||
null,
|
||||
background,
|
||||
scheme -> toneMinContrastDefault(tone, background, scheme, toneDeltaConstraint),
|
||||
scheme -> toneMaxContrastDefault(tone, background, scheme, toneDeltaConstraint),
|
||||
toneDeltaConstraint);
|
||||
}
|
||||
|
||||
/** Returns the ARGB (i.e. hex code) representation of the resolved color given scheme. */
|
||||
public int getArgb(DynamicScheme scheme) {
|
||||
final int argb = getHct(scheme).toInt();
|
||||
if (opacity == null) {
|
||||
return argb;
|
||||
}
|
||||
final double percentage = opacity.apply(scheme);
|
||||
final int alpha = MathUtils.clampInt(0, 255, (int) Math.round(percentage * 255));
|
||||
return (argb & 0x00ffffff) | (alpha << 24);
|
||||
}
|
||||
|
||||
/** Returns the HCT representation of the resolved color given scheme. */
|
||||
public Hct getHct(DynamicScheme scheme) {
|
||||
final Hct cachedAnswer = hctCache.get(scheme);
|
||||
if (cachedAnswer != null) {
|
||||
return cachedAnswer;
|
||||
}
|
||||
// This is crucial for aesthetics: we aren't simply the taking the standard color
|
||||
// and changing its tone for contrast. Rather, we find the tone for contrast, then
|
||||
// use the specified chroma from the palette to construct a new color.
|
||||
//
|
||||
// For example, this enables colors with standard tone of T90, which has limited chroma, to
|
||||
// "recover" intended chroma as contrast increases.
|
||||
final Hct answer = Hct.from(hue.apply(scheme), chroma.apply(scheme), getTone(scheme));
|
||||
// NOMUTANTS--trivial test with onerous dependency injection requirement.
|
||||
if (hctCache.size() > 4) {
|
||||
hctCache.clear();
|
||||
}
|
||||
// NOMUTANTS--trivial test with onerous dependency injection requirement.
|
||||
hctCache.put(scheme, answer);
|
||||
return answer;
|
||||
}
|
||||
|
||||
/** Returns the tone in HCT, ranging from 0 to 100, of the resolved color given scheme. */
|
||||
public double getTone(DynamicScheme scheme) {
|
||||
double answer = tone.apply(scheme);
|
||||
|
||||
final boolean decreasingContrast = scheme.contrastLevel < 0.0;
|
||||
if (scheme.contrastLevel != 0.0) {
|
||||
final double startTone = tone.apply(scheme);
|
||||
final double endTone =
|
||||
decreasingContrast ? toneMinContrast.apply(scheme) : toneMaxContrast.apply(
|
||||
scheme);
|
||||
final double delta = (endTone - startTone) * Math.abs(scheme.contrastLevel);
|
||||
answer = delta + startTone;
|
||||
}
|
||||
|
||||
final DynamicColor bgDynamicColor = background == null ? null : background.apply(scheme);
|
||||
double minRatio = Contrast.RATIO_MIN;
|
||||
double maxRatio = Contrast.RATIO_MAX;
|
||||
if (bgDynamicColor != null) {
|
||||
final boolean bgHasBg =
|
||||
bgDynamicColor.background != null && bgDynamicColor.background.apply(scheme)
|
||||
== null;
|
||||
final double standardRatio =
|
||||
Contrast.ratioOfTones(tone.apply(scheme), bgDynamicColor.tone.apply(scheme));
|
||||
if (decreasingContrast) {
|
||||
final double minContrastRatio =
|
||||
Contrast.ratioOfTones(
|
||||
toneMinContrast.apply(scheme),
|
||||
bgDynamicColor.toneMinContrast.apply(scheme));
|
||||
minRatio = bgHasBg ? 1.0 : minContrastRatio;
|
||||
maxRatio = standardRatio;
|
||||
} else {
|
||||
final double maxContrastRatio =
|
||||
Contrast.ratioOfTones(
|
||||
toneMaxContrast.apply(scheme),
|
||||
bgDynamicColor.toneMaxContrast.apply(scheme));
|
||||
minRatio = !bgHasBg ? 1.0 : min(maxContrastRatio, standardRatio);
|
||||
maxRatio = !bgHasBg ? 21.0 : max(maxContrastRatio, standardRatio);
|
||||
}
|
||||
}
|
||||
|
||||
final double finalMinRatio = minRatio;
|
||||
final double finalMaxRatio = maxRatio;
|
||||
final double finalAnswer = answer;
|
||||
answer =
|
||||
calculateDynamicTone(
|
||||
scheme,
|
||||
this.tone,
|
||||
(dynamicColor) -> dynamicColor.getTone(scheme),
|
||||
(a, b) -> finalAnswer,
|
||||
(s) -> bgDynamicColor,
|
||||
toneDeltaConstraint,
|
||||
(s) -> finalMinRatio,
|
||||
(s) -> finalMaxRatio);
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default algorithm for calculating the tone of a color at minimum contrast.<br>
|
||||
* If the original contrast ratio was >= 7.0, reach contrast 4.5.<br>
|
||||
* If the original contrast ratio was >= 3.0, reach contrast 3.0.<br>
|
||||
* If the original contrast ratio was < 3.0, reach that ratio.
|
||||
*/
|
||||
public static double toneMinContrastDefault(
|
||||
Function<DynamicScheme, Double> tone,
|
||||
Function<DynamicScheme, DynamicColor> background,
|
||||
DynamicScheme scheme,
|
||||
Function<DynamicScheme, ToneDeltaConstraint> toneDeltaConstraint) {
|
||||
return DynamicColor.calculateDynamicTone(
|
||||
scheme,
|
||||
tone,
|
||||
c -> c.toneMinContrast.apply(scheme),
|
||||
(stdRatio, bgTone) -> {
|
||||
double answer = tone.apply(scheme);
|
||||
if (stdRatio >= Contrast.RATIO_70) {
|
||||
answer = contrastingTone(bgTone, Contrast.RATIO_45);
|
||||
} else if (stdRatio >= Contrast.RATIO_30) {
|
||||
answer = contrastingTone(bgTone, Contrast.RATIO_30);
|
||||
} else {
|
||||
final boolean backgroundHasBackground =
|
||||
background != null
|
||||
&& background.apply(scheme) != null
|
||||
&& background.apply(scheme).background != null
|
||||
&& background.apply(scheme).background.apply(scheme)
|
||||
!= null;
|
||||
if (backgroundHasBackground) {
|
||||
answer = contrastingTone(bgTone, stdRatio);
|
||||
}
|
||||
}
|
||||
return answer;
|
||||
},
|
||||
background,
|
||||
toneDeltaConstraint,
|
||||
null,
|
||||
standardRatio -> standardRatio);
|
||||
}
|
||||
|
||||
/**
|
||||
* The default algorithm for calculating the tone of a color at maximum contrast.<br>
|
||||
* If the color's background has a background, reach contrast 7.0.<br>
|
||||
* If it doesn't, maintain the original contrast ratio.<br>
|
||||
*
|
||||
* <p>This ensures text on surfaces maintains its original, often detrimentally excessive,
|
||||
* contrast ratio. But, text on buttons can soften to not have excessive contrast.
|
||||
*
|
||||
* <p>Historically, digital design uses pure whites and black for text and surfaces. It's too
|
||||
* much
|
||||
* of a jump at this point in history to introduce a dynamic contrast system _and_ insist that
|
||||
* text always had excessive contrast and should reach 7.0, it would deterimentally affect
|
||||
* desire
|
||||
* to understand and use dynamic contrast.
|
||||
*/
|
||||
public static double toneMaxContrastDefault(
|
||||
Function<DynamicScheme, Double> tone,
|
||||
Function<DynamicScheme, DynamicColor> background,
|
||||
DynamicScheme scheme,
|
||||
Function<DynamicScheme, ToneDeltaConstraint> toneDeltaConstraint) {
|
||||
return DynamicColor.calculateDynamicTone(
|
||||
scheme,
|
||||
tone,
|
||||
c -> c.toneMaxContrast.apply(scheme),
|
||||
(stdRatio, bgTone) -> {
|
||||
final boolean backgroundHasBackground =
|
||||
background != null
|
||||
&& background.apply(scheme) != null
|
||||
&& background.apply(scheme).background != null
|
||||
&& background.apply(scheme).background.apply(scheme) != null;
|
||||
if (backgroundHasBackground) {
|
||||
return contrastingTone(bgTone, Contrast.RATIO_70);
|
||||
} else {
|
||||
return contrastingTone(bgTone, max(Contrast.RATIO_70, stdRatio));
|
||||
}
|
||||
},
|
||||
background,
|
||||
toneDeltaConstraint,
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Core method for calculating a tone for under dynamic contrast.
|
||||
*
|
||||
* <p>It enforces important properties:<br>
|
||||
* #1. Desired contrast ratio is reached.<br>
|
||||
* As contrast increases from standard to max, the tones involved should always be at least the
|
||||
* standard ratio. For example, if a button is T90, and button text is T0, and the button is T0
|
||||
* at
|
||||
* max contrast, the button text cannot simply linearly interpolate from T0 to T100, or at some
|
||||
* point they'll both be at the same tone.
|
||||
*
|
||||
* <p>#2. Enable light foregrounds on midtones.<br>
|
||||
* The eye prefers light foregrounds on T50 to T60, possibly up to T70, but, contrast ratio 4.5
|
||||
* can't be reached with T100 unless the foreground is T50. Contrast ratio 4.5 is crucial, it
|
||||
* represents 'readable text', i.e. text smaller than ~40 dp / 1/4". So, if a tone is between
|
||||
* T50
|
||||
* and T60, it is proactively changed to T49 to enable light foregrounds.
|
||||
*
|
||||
* <p>#3. Ensure tone delta with another color.<br>
|
||||
* In design systems, there may be colors that don't have a pure background/foreground
|
||||
* relationship, but, do require different tones for visual differentiation.
|
||||
* ToneDeltaConstraint
|
||||
* models this requirement, and DynamicColor enforces it.
|
||||
*/
|
||||
public static double calculateDynamicTone(
|
||||
DynamicScheme scheme,
|
||||
Function<DynamicScheme, Double> toneStandard,
|
||||
Function<DynamicColor, Double> toneToJudge,
|
||||
BiFunction<Double, Double, Double> desiredTone,
|
||||
Function<DynamicScheme, DynamicColor> background,
|
||||
Function<DynamicScheme, ToneDeltaConstraint> toneDeltaConstraint,
|
||||
Function<Double, Double> minRatio,
|
||||
Function<Double, Double> maxRatio) {
|
||||
// Start with the tone with no adjustment for contrast.
|
||||
// If there is no background, don't perform any adjustment, return immediately.
|
||||
final double toneStd = toneStandard.apply(scheme);
|
||||
double answer = toneStd;
|
||||
final DynamicColor bgDynamic = background == null ? null : background.apply(scheme);
|
||||
if (bgDynamic == null) {
|
||||
return answer;
|
||||
}
|
||||
final double bgToneStd = bgDynamic.tone.apply(scheme);
|
||||
final double stdRatio = Contrast.ratioOfTones(toneStd, bgToneStd);
|
||||
|
||||
// If there is a background, determine its tone after contrast adjustment.
|
||||
// Then, calculate the foreground tone that ensures the caller's desired contrast ratio
|
||||
// is met.
|
||||
final double bgTone = toneToJudge.apply(bgDynamic);
|
||||
final double myDesiredTone = desiredTone.apply(stdRatio, bgTone);
|
||||
final double currentRatio = Contrast.ratioOfTones(bgTone, myDesiredTone);
|
||||
final double minRatioRealized =
|
||||
minRatio == null
|
||||
? Contrast.RATIO_MIN
|
||||
: minRatio.apply(stdRatio) == null ? Contrast.RATIO_MIN : minRatio.apply(
|
||||
stdRatio);
|
||||
final double maxRatioRealized =
|
||||
maxRatio == null
|
||||
? Contrast.RATIO_MAX
|
||||
: maxRatio.apply(stdRatio) == null ? Contrast.RATIO_MAX : maxRatio.apply(
|
||||
stdRatio);
|
||||
final double desiredRatio =
|
||||
MathUtils.clampDouble(minRatioRealized, maxRatioRealized, currentRatio);
|
||||
if (desiredRatio == currentRatio) {
|
||||
answer = myDesiredTone;
|
||||
} else {
|
||||
answer = DynamicColor.contrastingTone(bgTone, desiredRatio);
|
||||
}
|
||||
|
||||
// If the background has no background, adjust the foreground tone to ensure that
|
||||
// it is dark enough to have a light foreground.
|
||||
if (bgDynamic.background == null || bgDynamic.background.apply(scheme) == null) {
|
||||
answer = DynamicColor.enableLightForeground(answer);
|
||||
}
|
||||
|
||||
// If the caller has specified a constraint where it must have a certain tone distance from
|
||||
// another color, enforce that constraint.
|
||||
answer = ensureToneDelta(answer, toneStd, scheme, toneDeltaConstraint, toneToJudge);
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
static double ensureToneDelta(
|
||||
double tone,
|
||||
double toneStandard,
|
||||
DynamicScheme scheme,
|
||||
Function<DynamicScheme, ToneDeltaConstraint> toneDeltaConstraint,
|
||||
Function<DynamicColor, Double> toneToDistanceFrom) {
|
||||
final ToneDeltaConstraint constraint =
|
||||
(toneDeltaConstraint == null ? null : toneDeltaConstraint.apply(scheme));
|
||||
if (constraint == null) {
|
||||
return tone;
|
||||
}
|
||||
|
||||
final double requiredDelta = constraint.delta;
|
||||
final double keepAwayTone = toneToDistanceFrom.apply(constraint.keepAway);
|
||||
final double delta = Math.abs(tone - keepAwayTone);
|
||||
if (delta >= requiredDelta) {
|
||||
return tone;
|
||||
}
|
||||
switch (constraint.keepAwayPolarity) {
|
||||
case DARKER:
|
||||
return MathUtils.clampDouble(0, 100, keepAwayTone + requiredDelta);
|
||||
case LIGHTER:
|
||||
return MathUtils.clampDouble(0, 100, keepAwayTone - requiredDelta);
|
||||
case NO_PREFERENCE:
|
||||
final double keepAwayToneStandard = constraint.keepAway.tone.apply(scheme);
|
||||
final boolean preferLighten = toneStandard > keepAwayToneStandard;
|
||||
final double alterAmount = Math.abs(delta - requiredDelta);
|
||||
final boolean lighten =
|
||||
preferLighten ? (tone + alterAmount <= 100.0) : tone < alterAmount;
|
||||
return lighten ? tone + alterAmount : tone - alterAmount;
|
||||
}
|
||||
return tone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a background tone, find a foreground tone, while ensuring they reach a contrast ratio
|
||||
* that is as close to ratio as possible.
|
||||
*/
|
||||
public static double contrastingTone(double bgTone, double ratio) {
|
||||
final double lighterTone = Contrast.lighterUnsafe(bgTone, ratio);
|
||||
final double darkerTone = Contrast.darkerUnsafe(bgTone, ratio);
|
||||
final double lighterRatio = Contrast.ratioOfTones(lighterTone, bgTone);
|
||||
final double darkerRatio = Contrast.ratioOfTones(darkerTone, bgTone);
|
||||
final boolean preferLighter = tonePrefersLightForeground(bgTone);
|
||||
|
||||
if (preferLighter) {
|
||||
// "Negligible difference" handles an edge case where the initial contrast ratio is high
|
||||
// (ex. 13.0), and the ratio passed to the function is that high ratio, and both the
|
||||
// lighter and darker ratio fails to pass that ratio.
|
||||
//
|
||||
// This was observed with Tonal Spot's On Primary Container turning black momentarily
|
||||
// between high and max contrast in light mode. PC's standard tone was T90, OPC's was
|
||||
// T10, it was light mode, and the contrast level was 0.6568521221032331.
|
||||
final boolean negligibleDifference =
|
||||
Math.abs(lighterRatio - darkerRatio) < 0.1 && lighterRatio < ratio
|
||||
&& darkerRatio < ratio;
|
||||
if (lighterRatio >= ratio || lighterRatio >= darkerRatio || negligibleDifference) {
|
||||
return lighterTone;
|
||||
} else {
|
||||
return darkerTone;
|
||||
}
|
||||
} else {
|
||||
return darkerRatio >= ratio || darkerRatio >= lighterRatio ? darkerTone : lighterTone;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust a tone down such that white has 4.5 contrast, if the tone is reasonably close to
|
||||
* supporting it.
|
||||
*/
|
||||
public static double enableLightForeground(double tone) {
|
||||
if (tonePrefersLightForeground(tone) && !toneAllowsLightForeground(tone)) {
|
||||
return 49.0;
|
||||
}
|
||||
return tone;
|
||||
}
|
||||
|
||||
/**
|
||||
* People prefer white foregrounds on ~T60-70. Observed over time, and also by Andrew Somers
|
||||
* during research for APCA.
|
||||
*
|
||||
* <p>T60 used as to create the smallest discontinuity possible when skipping down to T49 in
|
||||
* order
|
||||
* to ensure light foregrounds.
|
||||
*/
|
||||
public static boolean tonePrefersLightForeground(double tone) {
|
||||
return Math.round(tone) <= 60;
|
||||
}
|
||||
|
||||
/** Tones less than ~T50 always permit white at 4.5 contrast. */
|
||||
public static boolean toneAllowsLightForeground(double tone) {
|
||||
return Math.round(tone) <= 49;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,415 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.dynamiccolor;
|
||||
|
||||
import com.android.systemui.monet.dislike.DislikeAnalyzer;
|
||||
import com.android.systemui.monet.dynamiccolor.DynamicColor;
|
||||
import com.android.systemui.monet.dynamiccolor.ToneDeltaConstraint;
|
||||
import com.android.systemui.monet.dynamiccolor.TonePolarity;
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.hct.ViewingConditions;
|
||||
import com.android.systemui.monet.scheme.DynamicScheme;
|
||||
import com.android.systemui.monet.scheme.Variant;
|
||||
|
||||
/** Named colors, otherwise known as tokens, or roles, in the Material Design system. */
|
||||
public final class MaterialDynamicColors {
|
||||
private static final double CONTAINER_ACCENT_TONE_DELTA = 15.0;
|
||||
|
||||
|
||||
private MaterialDynamicColors() {
|
||||
}
|
||||
|
||||
/** In light mode, the darkest surface. In dark mode, the lightest surface. */
|
||||
public static DynamicColor highestSurface(DynamicScheme s) {
|
||||
return s.isDark ? surfaceBright : surfaceDim;
|
||||
}
|
||||
|
||||
public static final DynamicColor background =
|
||||
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 6.0 : 98.0);
|
||||
|
||||
public static final DynamicColor onBackground =
|
||||
DynamicColor.fromPalette(
|
||||
(s) -> s.neutralPalette, (s) -> s.isDark ? 90.0 : 10.0, (s) -> background);
|
||||
|
||||
public static final DynamicColor surface =
|
||||
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 6.0 : 98.0);
|
||||
|
||||
public static final DynamicColor surfaceInverse =
|
||||
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 90.0 : 20.0);
|
||||
|
||||
public static final DynamicColor surfaceBright =
|
||||
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 24.0 : 98.0);
|
||||
|
||||
public static final DynamicColor surfaceDim =
|
||||
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 6.0 : 87.0);
|
||||
|
||||
public static final DynamicColor surfaceSub2 =
|
||||
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 4.0 : 100.0);
|
||||
|
||||
public static final DynamicColor surfaceSub1 =
|
||||
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 10.0 : 96.0);
|
||||
|
||||
public static final DynamicColor surfaceContainer =
|
||||
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 12.0 : 94.0);
|
||||
|
||||
public static final DynamicColor surfaceAdd1 =
|
||||
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 17.0 : 92.0);
|
||||
|
||||
public static final DynamicColor surfaceAdd2 =
|
||||
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 22.0 : 90.0);
|
||||
|
||||
public static final DynamicColor onSurface =
|
||||
DynamicColor.fromPalette(
|
||||
(s) -> s.neutralPalette, (s) -> s.isDark ? 90.0 : 10.0,
|
||||
(s) -> highestSurface(s));
|
||||
|
||||
public static final DynamicColor onSurfaceInverse =
|
||||
DynamicColor.fromPalette(
|
||||
(s) -> s.neutralPalette, (s) -> s.isDark ? 20.0 : 95.0, (s) -> surfaceInverse);
|
||||
|
||||
public static final DynamicColor surfaceVariant =
|
||||
DynamicColor.fromPalette((s) -> s.neutralVariantPalette, (s) -> s.isDark ? 30.0 : 90.0);
|
||||
|
||||
public static final DynamicColor onSurfaceVariant =
|
||||
DynamicColor.fromPalette(
|
||||
(s) -> s.neutralVariantPalette, (s) -> s.isDark ? 80.0 : 30.0,
|
||||
(s) -> surfaceVariant);
|
||||
|
||||
public static final DynamicColor outline =
|
||||
DynamicColor.fromPalette(
|
||||
(s) -> s.neutralVariantPalette, (s) -> 50.0, (s) -> highestSurface(s));
|
||||
|
||||
public static final DynamicColor outlineVariant =
|
||||
DynamicColor.fromPalette(
|
||||
(s) -> s.neutralVariantPalette, (s) -> 80.0, (s) -> highestSurface(s));
|
||||
|
||||
public static final DynamicColor primaryContainer =
|
||||
DynamicColor.fromPalette(
|
||||
(s) -> s.primaryPalette,
|
||||
(s) -> {
|
||||
if (!isFidelity(s)) {
|
||||
return s.isDark ? 30.0 : 90.0;
|
||||
}
|
||||
return performAlbers(s.sourceColorHct, s);
|
||||
},
|
||||
(s) -> highestSurface(s));
|
||||
|
||||
public static final DynamicColor onPrimaryContainer =
|
||||
DynamicColor.fromPalette(
|
||||
(s) -> s.primaryPalette,
|
||||
(s) -> {
|
||||
if (!isFidelity(s)) {
|
||||
return s.isDark ? 90.0 : 10.0;
|
||||
}
|
||||
return DynamicColor.contrastingTone(primaryContainer.getTone(s), 4.5);
|
||||
},
|
||||
(s) -> primaryContainer,
|
||||
null);
|
||||
|
||||
public static final DynamicColor primary =
|
||||
DynamicColor.fromPalette(
|
||||
(s) -> s.primaryPalette,
|
||||
(s) -> s.isDark ? 80.0 : 40.0,
|
||||
(s) -> highestSurface(s),
|
||||
(s) ->
|
||||
new ToneDeltaConstraint(
|
||||
CONTAINER_ACCENT_TONE_DELTA,
|
||||
primaryContainer,
|
||||
s.isDark ? TonePolarity.DARKER : TonePolarity.LIGHTER));
|
||||
|
||||
public static final DynamicColor primaryInverse =
|
||||
DynamicColor.fromPalette(
|
||||
(s) -> s.primaryPalette, (s) -> s.isDark ? 40.0 : 80.0, (s) -> surfaceInverse);
|
||||
|
||||
public static final DynamicColor onPrimary =
|
||||
DynamicColor.fromPalette(
|
||||
(s) -> s.primaryPalette, (s) -> s.isDark ? 20.0 : 100.0, (s) -> primary);
|
||||
|
||||
public static final DynamicColor secondaryContainer =
|
||||
DynamicColor.fromPalette(
|
||||
(s) -> s.secondaryPalette,
|
||||
(s) -> {
|
||||
final double initialTone = s.isDark ? 30.0 : 90.0;
|
||||
if (!isFidelity(s)) {
|
||||
return initialTone;
|
||||
}
|
||||
double answer =
|
||||
findDesiredChromaByTone(
|
||||
s.secondaryPalette.getHue(),
|
||||
s.secondaryPalette.getChroma(),
|
||||
initialTone,
|
||||
!s.isDark);
|
||||
answer = performAlbers(s.secondaryPalette.getHct(answer), s);
|
||||
return answer;
|
||||
},
|
||||
(s) -> highestSurface(s));
|
||||
|
||||
public static final DynamicColor onSecondaryContainer =
|
||||
DynamicColor.fromPalette(
|
||||
(s) -> s.secondaryPalette,
|
||||
(s) -> {
|
||||
if (!isFidelity(s)) {
|
||||
return s.isDark ? 90.0 : 10.0;
|
||||
}
|
||||
return DynamicColor.contrastingTone(secondaryContainer.getTone(s), 4.5);
|
||||
},
|
||||
(s) -> secondaryContainer);
|
||||
|
||||
public static final DynamicColor secondary =
|
||||
DynamicColor.fromPalette(
|
||||
(s) -> s.secondaryPalette,
|
||||
(s) -> s.isDark ? 80.0 : 40.0,
|
||||
(s) -> highestSurface(s),
|
||||
(s) ->
|
||||
new ToneDeltaConstraint(
|
||||
CONTAINER_ACCENT_TONE_DELTA,
|
||||
secondaryContainer,
|
||||
s.isDark ? TonePolarity.DARKER : TonePolarity.LIGHTER));
|
||||
|
||||
public static final DynamicColor onSecondary =
|
||||
DynamicColor.fromPalette(
|
||||
(s) -> s.secondaryPalette, (s) -> s.isDark ? 20.0 : 100.0, (s) -> secondary);
|
||||
|
||||
public static final DynamicColor tertiaryContainer =
|
||||
DynamicColor.fromPalette(
|
||||
(s) -> s.tertiaryPalette,
|
||||
(s) -> {
|
||||
if (!isFidelity(s)) {
|
||||
return s.isDark ? 30.0 : 90.0;
|
||||
}
|
||||
final double albersTone =
|
||||
performAlbers(s.tertiaryPalette.getHct(s.sourceColorHct.getTone()),
|
||||
s);
|
||||
final Hct proposedHct = s.tertiaryPalette.getHct(albersTone);
|
||||
return DislikeAnalyzer.fixIfDisliked(proposedHct).getTone();
|
||||
},
|
||||
(s) -> highestSurface(s));
|
||||
|
||||
public static final DynamicColor onTertiaryContainer =
|
||||
DynamicColor.fromPalette(
|
||||
(s) -> s.tertiaryPalette,
|
||||
(s) -> {
|
||||
if (!isFidelity(s)) {
|
||||
return s.isDark ? 90.0 : 10.0;
|
||||
}
|
||||
return DynamicColor.contrastingTone(tertiaryContainer.getTone(s), 4.5);
|
||||
},
|
||||
(s) -> tertiaryContainer);
|
||||
|
||||
public static final DynamicColor tertiary =
|
||||
DynamicColor.fromPalette(
|
||||
(s) -> s.tertiaryPalette,
|
||||
(s) -> s.isDark ? 80.0 : 40.0,
|
||||
(s) -> highestSurface(s),
|
||||
(s) ->
|
||||
new ToneDeltaConstraint(
|
||||
CONTAINER_ACCENT_TONE_DELTA,
|
||||
tertiaryContainer,
|
||||
s.isDark ? TonePolarity.DARKER : TonePolarity.LIGHTER));
|
||||
|
||||
public static final DynamicColor onTertiary =
|
||||
DynamicColor.fromPalette(
|
||||
(s) -> s.tertiaryPalette, (s) -> s.isDark ? 20.0 : 100.0, (s) -> tertiary);
|
||||
|
||||
public static final DynamicColor errorContainer =
|
||||
DynamicColor.fromPalette(
|
||||
(s) -> s.errorPalette, (s) -> s.isDark ? 30.0 : 90.0, (s) -> highestSurface(s));
|
||||
|
||||
public static final DynamicColor onErrorContainer =
|
||||
DynamicColor.fromPalette(
|
||||
(s) -> s.errorPalette, (s) -> s.isDark ? 90.0 : 10.0, (s) -> errorContainer);
|
||||
|
||||
public static final DynamicColor error =
|
||||
DynamicColor.fromPalette(
|
||||
(s) -> s.errorPalette,
|
||||
(s) -> s.isDark ? 80.0 : 40.0,
|
||||
(s) -> highestSurface(s),
|
||||
(s) ->
|
||||
new ToneDeltaConstraint(
|
||||
CONTAINER_ACCENT_TONE_DELTA,
|
||||
errorContainer,
|
||||
s.isDark ? TonePolarity.DARKER : TonePolarity.LIGHTER));
|
||||
|
||||
public static final DynamicColor onError =
|
||||
DynamicColor.fromPalette((s) -> s.errorPalette, (s) -> s.isDark ? 20.0 : 100.0,
|
||||
(s) -> error);
|
||||
|
||||
public static final DynamicColor primaryFixed =
|
||||
DynamicColor.fromPalette((s) -> s.primaryPalette, (s) -> 90.0,
|
||||
(s) -> highestSurface(s));
|
||||
|
||||
public static final DynamicColor primaryFixedDarker =
|
||||
DynamicColor.fromPalette((s) -> s.primaryPalette, (s) -> 80.0,
|
||||
(s) -> highestSurface(s));
|
||||
|
||||
public static final DynamicColor onPrimaryFixed =
|
||||
DynamicColor.fromPalette((s) -> s.primaryPalette, (s) -> 10.0,
|
||||
(s) -> primaryFixedDarker);
|
||||
|
||||
public static final DynamicColor onPrimaryFixedVariant =
|
||||
DynamicColor.fromPalette((s) -> s.primaryPalette, (s) -> 30.0,
|
||||
(s) -> primaryFixedDarker);
|
||||
|
||||
public static final DynamicColor secondaryFixed =
|
||||
DynamicColor.fromPalette((s) -> s.secondaryPalette, (s) -> 90.0,
|
||||
(s) -> highestSurface(s));
|
||||
|
||||
public static final DynamicColor secondaryFixedDarker =
|
||||
DynamicColor.fromPalette((s) -> s.secondaryPalette, (s) -> 80.0,
|
||||
(s) -> highestSurface(s));
|
||||
|
||||
public static final DynamicColor onSecondaryFixed =
|
||||
DynamicColor.fromPalette((s) -> s.secondaryPalette, (s) -> 10.0,
|
||||
(s) -> secondaryFixedDarker);
|
||||
|
||||
public static final DynamicColor onSecondaryFixedVariant =
|
||||
DynamicColor.fromPalette((s) -> s.secondaryPalette, (s) -> 30.0,
|
||||
(s) -> secondaryFixedDarker);
|
||||
|
||||
public static final DynamicColor tertiaryFixed =
|
||||
DynamicColor.fromPalette((s) -> s.tertiaryPalette, (s) -> 90.0,
|
||||
(s) -> highestSurface(s));
|
||||
|
||||
public static final DynamicColor tertiaryFixedDarker =
|
||||
DynamicColor.fromPalette((s) -> s.tertiaryPalette, (s) -> 80.0,
|
||||
(s) -> highestSurface(s));
|
||||
|
||||
public static final DynamicColor onTertiaryFixed =
|
||||
DynamicColor.fromPalette((s) -> s.tertiaryPalette, (s) -> 10.0,
|
||||
(s) -> tertiaryFixedDarker);
|
||||
|
||||
public static final DynamicColor onTertiaryFixedVariant =
|
||||
DynamicColor.fromPalette((s) -> s.tertiaryPalette, (s) -> 30.0,
|
||||
(s) -> tertiaryFixedDarker);
|
||||
|
||||
/**
|
||||
* These colors were present in Android framework before Android U, and used by MDC controls.
|
||||
* They
|
||||
* should be avoided, if possible. It's unclear if they're used on multiple backgrounds, and if
|
||||
* they are, they can't be adjusted for contrast.* For now, they will be set with no
|
||||
* background,
|
||||
* and those won't adjust for contrast, avoiding issues.
|
||||
*
|
||||
* <p>* For example, if the same color is on a white background _and_ black background, there's
|
||||
* no
|
||||
* way to increase contrast with either without losing contrast with the other.
|
||||
*/
|
||||
// colorControlActivated documented as colorAccent in M3 & GM3.
|
||||
// colorAccent documented as colorSecondary in M3 and colorPrimary in GM3.
|
||||
// Android used Material's Container as Primary/Secondary/Tertiary at launch.
|
||||
// Therefore, this is a duplicated version of Primary Container.
|
||||
public static final DynamicColor controlActivated =
|
||||
DynamicColor.fromPalette((s) -> s.primaryPalette, (s) -> s.isDark ? 30.0 : 90.0, null);
|
||||
|
||||
// colorControlNormal documented as textColorSecondary in M3 & GM3.
|
||||
// In Material, textColorSecondary points to onSurfaceVariant in the non-disabled state,
|
||||
// which is Neutral Variant T30/80 in light/dark.
|
||||
public static final DynamicColor controlNormal =
|
||||
DynamicColor.fromPalette((s) -> s.neutralVariantPalette, (s) -> s.isDark ? 80.0 : 30.0);
|
||||
|
||||
// colorControlHighlight documented, in both M3 & GM3:
|
||||
// Light mode: #1f000000 dark mode: #33ffffff.
|
||||
// These are black and white with some alpha.
|
||||
// 1F hex = 31 decimal; 31 / 255 = 12% alpha.
|
||||
// 33 hex = 51 decimal; 51 / 255 = 20% alpha.
|
||||
// DynamicColors do not support alpha currently, and _may_ not need it for this use case,
|
||||
// depending on how MDC resolved alpha for the other cases.
|
||||
// Returning black in dark mode, white in light mode.
|
||||
public static final DynamicColor controlHighlight =
|
||||
new DynamicColor(
|
||||
s -> 0.0,
|
||||
s -> 0.0,
|
||||
s -> s.isDark ? 100.0 : 0.0,
|
||||
s -> s.isDark ? 0.20 : 0.12,
|
||||
null,
|
||||
scheme ->
|
||||
DynamicColor.toneMinContrastDefault(
|
||||
(s) -> s.isDark ? 100.0 : 0.0, null, scheme, null),
|
||||
scheme ->
|
||||
DynamicColor.toneMaxContrastDefault(
|
||||
(s) -> s.isDark ? 100.0 : 0.0, null, scheme, null),
|
||||
null);
|
||||
|
||||
// textColorPrimaryInverse documented, in both M3 & GM3, documented as N10/N90.
|
||||
public static final DynamicColor textPrimaryInverse =
|
||||
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 10.0 : 90.0);
|
||||
|
||||
// textColorSecondaryInverse and textColorTertiaryInverse both documented, in both M3 & GM3, as
|
||||
// NV30/NV80
|
||||
public static final DynamicColor textSecondaryAndTertiaryInverse =
|
||||
DynamicColor.fromPalette((s) -> s.neutralVariantPalette, (s) -> s.isDark ? 30.0 : 80.0);
|
||||
|
||||
// textColorPrimaryInverseDisableOnly documented, in both M3 & GM3, as N10/N90
|
||||
public static final DynamicColor textPrimaryInverseDisableOnly =
|
||||
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 10.0 : 90.0);
|
||||
|
||||
// textColorSecondaryInverse and textColorTertiaryInverse in disabled state both documented,
|
||||
// in both M3 & GM3, as N10/N90
|
||||
public static final DynamicColor textSecondaryAndTertiaryInverseDisabled =
|
||||
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 10.0 : 90.0);
|
||||
|
||||
// textColorHintInverse documented, in both M3 & GM3, as N10/N90
|
||||
public static final DynamicColor textHintInverse =
|
||||
DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 10.0 : 90.0);
|
||||
|
||||
private static ViewingConditions viewingConditionsForAlbers(DynamicScheme scheme) {
|
||||
return ViewingConditions.defaultWithBackgroundLstar(scheme.isDark ? 30.0 : 80.0);
|
||||
}
|
||||
|
||||
private static boolean isFidelity(DynamicScheme scheme) {
|
||||
return scheme.variant == Variant.FIDELITY || scheme.variant == Variant.CONTENT;
|
||||
}
|
||||
|
||||
static double findDesiredChromaByTone(
|
||||
double hue, double chroma, double tone, boolean byDecreasingTone) {
|
||||
double answer = tone;
|
||||
|
||||
Hct closestToChroma = Hct.from(hue, chroma, tone);
|
||||
if (closestToChroma.getChroma() < chroma) {
|
||||
double chromaPeak = closestToChroma.getChroma();
|
||||
while (closestToChroma.getChroma() < chroma) {
|
||||
answer += byDecreasingTone ? -1.0 : 1.0;
|
||||
Hct potentialSolution = Hct.from(hue, chroma, answer);
|
||||
if (chromaPeak > potentialSolution.getChroma()) {
|
||||
break;
|
||||
}
|
||||
if (Math.abs(potentialSolution.getChroma() - chroma) < 0.4) {
|
||||
break;
|
||||
}
|
||||
|
||||
double potentialDelta = Math.abs(potentialSolution.getChroma() - chroma);
|
||||
double currentDelta = Math.abs(closestToChroma.getChroma() - chroma);
|
||||
if (potentialDelta < currentDelta) {
|
||||
closestToChroma = potentialSolution;
|
||||
}
|
||||
chromaPeak = Math.max(chromaPeak, potentialSolution.getChroma());
|
||||
}
|
||||
}
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
static double performAlbers(Hct prealbers, DynamicScheme scheme) {
|
||||
final Hct albersd = prealbers.inViewingConditions(viewingConditionsForAlbers(scheme));
|
||||
if (DynamicColor.tonePrefersLightForeground(prealbers.getTone())
|
||||
&& !DynamicColor.toneAllowsLightForeground(albersd.getTone())) {
|
||||
return DynamicColor.enableLightForeground(prealbers.getTone());
|
||||
} else {
|
||||
return DynamicColor.enableLightForeground(albersd.getTone());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.dynamiccolor;
|
||||
|
||||
/**
|
||||
* Documents a constraint between two DynamicColors, in which their tones must have a certain
|
||||
* distance from each other.
|
||||
*/
|
||||
public final class ToneDeltaConstraint {
|
||||
public final double delta;
|
||||
public final DynamicColor keepAway;
|
||||
public final TonePolarity keepAwayPolarity;
|
||||
|
||||
/**
|
||||
* @param delta the difference in tone required
|
||||
* @param keepAway the color to distance in tone from
|
||||
* @param keepAwayPolarity whether the color to keep away from must be lighter, darker, or no
|
||||
* preference, in which case it should
|
||||
*/
|
||||
public ToneDeltaConstraint(double delta, DynamicColor keepAway, TonePolarity keepAwayPolarity) {
|
||||
this.delta = delta;
|
||||
this.keepAway = keepAway;
|
||||
this.keepAwayPolarity = keepAwayPolarity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.dynamiccolor;
|
||||
|
||||
/** Describes the relationship in lightness between two colors. */
|
||||
public enum TonePolarity {
|
||||
DARKER,
|
||||
LIGHTER,
|
||||
NO_PREFERENCE;
|
||||
}
|
||||
@@ -0,0 +1,454 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.hct;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
|
||||
import com.android.systemui.monet.utils.ColorUtils;
|
||||
|
||||
/**
|
||||
* CAM16, a color appearance model. Colors are not just defined by their hex code, but rather, a hex
|
||||
* code and viewing conditions.
|
||||
*
|
||||
* <p>CAM16 instances also have coordinates in the CAM16-UCS space, called J*, a*, b*, or jstar,
|
||||
* astar, bstar in code. CAM16-UCS is included in the CAM16 specification, and should be used when
|
||||
* measuring distances between colors.
|
||||
*
|
||||
* <p>In traditional color spaces, a color can be identified solely by the observer's measurement of
|
||||
* the color. Color appearance models such as CAM16 also use information about the environment where
|
||||
* the color was observed, known as the viewing conditions.
|
||||
*
|
||||
* <p>For example, white under the traditional assumption of a midday sun white point is accurately
|
||||
* measured as a slightly chromatic blue by CAM16. (roughly, hue 203, chroma 3, lightness 100)
|
||||
*/
|
||||
public final class Cam16 {
|
||||
// Transforms XYZ color space coordinates to 'cone'/'RGB' responses in CAM16.
|
||||
static final double[][] XYZ_TO_CAM16RGB = {
|
||||
{0.401288, 0.650173, -0.051461},
|
||||
{-0.250268, 1.204414, 0.045854},
|
||||
{-0.002079, 0.048952, 0.953127}
|
||||
};
|
||||
|
||||
// Transforms 'cone'/'RGB' responses in CAM16 to XYZ color space coordinates.
|
||||
static final double[][] CAM16RGB_TO_XYZ = {
|
||||
{1.8620678, -1.0112547, 0.14918678},
|
||||
{0.38752654, 0.62144744, -0.00897398},
|
||||
{-0.01584150, -0.03412294, 1.0499644}
|
||||
};
|
||||
|
||||
// CAM16 color dimensions, see getters for documentation.
|
||||
private final double hue;
|
||||
private final double chroma;
|
||||
private final double j;
|
||||
private final double q;
|
||||
private final double m;
|
||||
private final double s;
|
||||
|
||||
// Coordinates in UCS space. Used to determine color distance, like delta E equations in L*a*b*.
|
||||
private final double jstar;
|
||||
private final double astar;
|
||||
private final double bstar;
|
||||
|
||||
// Avoid allocations during conversion by pre-allocating an array.
|
||||
private final double[] tempArray = new double[]{0.0, 0.0, 0.0};
|
||||
|
||||
/**
|
||||
* CAM16 instances also have coordinates in the CAM16-UCS space, called J*, a*, b*, or jstar,
|
||||
* astar, bstar in code. CAM16-UCS is included in the CAM16 specification, and is used to
|
||||
* measure
|
||||
* distances between colors.
|
||||
*/
|
||||
double distance(Cam16 other) {
|
||||
double dJ = getJstar() - other.getJstar();
|
||||
double dA = getAstar() - other.getAstar();
|
||||
double dB = getBstar() - other.getBstar();
|
||||
double dEPrime = Math.sqrt(dJ * dJ + dA * dA + dB * dB);
|
||||
double dE = 1.41 * Math.pow(dEPrime, 0.63);
|
||||
return dE;
|
||||
}
|
||||
|
||||
/** Hue in CAM16 */
|
||||
public double getHue() {
|
||||
return hue;
|
||||
}
|
||||
|
||||
/** Chroma in CAM16 */
|
||||
public double getChroma() {
|
||||
return chroma;
|
||||
}
|
||||
|
||||
/** Lightness in CAM16 */
|
||||
public double getJ() {
|
||||
return j;
|
||||
}
|
||||
|
||||
/**
|
||||
* Brightness in CAM16.
|
||||
*
|
||||
* <p>Prefer lightness, brightness is an absolute quantity. For example, a sheet of white paper
|
||||
* is
|
||||
* much brighter viewed in sunlight than in indoor light, but it is the lightest object under
|
||||
* any
|
||||
* lighting.
|
||||
*/
|
||||
public double getQ() {
|
||||
return q;
|
||||
}
|
||||
|
||||
/**
|
||||
* Colorfulness in CAM16.
|
||||
*
|
||||
* <p>Prefer chroma, colorfulness is an absolute quantity. For example, a yellow toy car is much
|
||||
* more colorful outside than inside, but it has the same chroma in both environments.
|
||||
*/
|
||||
public double getM() {
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saturation in CAM16.
|
||||
*
|
||||
* <p>Colorfulness in proportion to brightness. Prefer chroma, saturation measures colorfulness
|
||||
* relative to the color's own brightness, where chroma is colorfulness relative to white.
|
||||
*/
|
||||
public double getS() {
|
||||
return s;
|
||||
}
|
||||
|
||||
/** Lightness coordinate in CAM16-UCS */
|
||||
public double getJstar() {
|
||||
return jstar;
|
||||
}
|
||||
|
||||
/** a* coordinate in CAM16-UCS */
|
||||
public double getAstar() {
|
||||
return astar;
|
||||
}
|
||||
|
||||
/** b* coordinate in CAM16-UCS */
|
||||
public double getBstar() {
|
||||
return bstar;
|
||||
}
|
||||
|
||||
/**
|
||||
* All of the CAM16 dimensions can be calculated from 3 of the dimensions, in the following
|
||||
* combinations: - {j or q} and {c, m, or s} and hue - jstar, astar, bstar Prefer using a static
|
||||
* method that constructs from 3 of those dimensions. This constructor is intended for those
|
||||
* methods to use to return all possible dimensions.
|
||||
*
|
||||
* @param hue for example, red, orange, yellow, green, etc.
|
||||
* @param chroma informally, colorfulness / color intensity. like saturation in HSL, except
|
||||
* perceptually accurate.
|
||||
* @param j lightness
|
||||
* @param q brightness; ratio of lightness to white point's lightness
|
||||
* @param m colorfulness
|
||||
* @param s saturation; ratio of chroma to white point's chroma
|
||||
* @param jstar CAM16-UCS J coordinate
|
||||
* @param astar CAM16-UCS a coordinate
|
||||
* @param bstar CAM16-UCS b coordinate
|
||||
*/
|
||||
private Cam16(
|
||||
double hue,
|
||||
double chroma,
|
||||
double j,
|
||||
double q,
|
||||
double m,
|
||||
double s,
|
||||
double jstar,
|
||||
double astar,
|
||||
double bstar) {
|
||||
this.hue = hue;
|
||||
this.chroma = chroma;
|
||||
this.j = j;
|
||||
this.q = q;
|
||||
this.m = m;
|
||||
this.s = s;
|
||||
this.jstar = jstar;
|
||||
this.astar = astar;
|
||||
this.bstar = bstar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a CAM16 color from a color, assuming the color was viewed in default viewing
|
||||
* conditions.
|
||||
*
|
||||
* @param argb ARGB representation of a color.
|
||||
*/
|
||||
public static Cam16 fromInt(int argb) {
|
||||
return fromIntInViewingConditions(argb, ViewingConditions.DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a CAM16 color from a color in defined viewing conditions.
|
||||
*
|
||||
* @param argb ARGB representation of a color.
|
||||
* @param viewingConditions Information about the environment where the color was observed.
|
||||
*/
|
||||
// The RGB => XYZ conversion matrix elements are derived scientific constants. While the values
|
||||
// may differ at runtime due to floating point imprecision, keeping the values the same, and
|
||||
// accurate, across implementations takes precedence.
|
||||
@SuppressWarnings("FloatingPointLiteralPrecision")
|
||||
static Cam16 fromIntInViewingConditions(int argb, ViewingConditions viewingConditions) {
|
||||
// Transform ARGB int to XYZ
|
||||
int red = (argb & 0x00ff0000) >> 16;
|
||||
int green = (argb & 0x0000ff00) >> 8;
|
||||
int blue = (argb & 0x000000ff);
|
||||
double redL = ColorUtils.linearized(red);
|
||||
double greenL = ColorUtils.linearized(green);
|
||||
double blueL = ColorUtils.linearized(blue);
|
||||
double x = 0.41233895 * redL + 0.35762064 * greenL + 0.18051042 * blueL;
|
||||
double y = 0.2126 * redL + 0.7152 * greenL + 0.0722 * blueL;
|
||||
double z = 0.01932141 * redL + 0.11916382 * greenL + 0.95034478 * blueL;
|
||||
|
||||
return fromXyzInViewingConditions(x, y, z, viewingConditions);
|
||||
}
|
||||
|
||||
static Cam16 fromXyzInViewingConditions(
|
||||
double x, double y, double z, ViewingConditions viewingConditions) {
|
||||
// Transform XYZ to 'cone'/'rgb' responses
|
||||
double[][] matrix = XYZ_TO_CAM16RGB;
|
||||
double rT = (x * matrix[0][0]) + (y * matrix[0][1]) + (z * matrix[0][2]);
|
||||
double gT = (x * matrix[1][0]) + (y * matrix[1][1]) + (z * matrix[1][2]);
|
||||
double bT = (x * matrix[2][0]) + (y * matrix[2][1]) + (z * matrix[2][2]);
|
||||
|
||||
// Discount illuminant
|
||||
double rD = viewingConditions.getRgbD()[0] * rT;
|
||||
double gD = viewingConditions.getRgbD()[1] * gT;
|
||||
double bD = viewingConditions.getRgbD()[2] * bT;
|
||||
|
||||
// Chromatic adaptation
|
||||
double rAF = Math.pow(viewingConditions.getFl() * Math.abs(rD) / 100.0, 0.42);
|
||||
double gAF = Math.pow(viewingConditions.getFl() * Math.abs(gD) / 100.0, 0.42);
|
||||
double bAF = Math.pow(viewingConditions.getFl() * Math.abs(bD) / 100.0, 0.42);
|
||||
double rA = Math.signum(rD) * 400.0 * rAF / (rAF + 27.13);
|
||||
double gA = Math.signum(gD) * 400.0 * gAF / (gAF + 27.13);
|
||||
double bA = Math.signum(bD) * 400.0 * bAF / (bAF + 27.13);
|
||||
|
||||
// redness-greenness
|
||||
double a = (11.0 * rA + -12.0 * gA + bA) / 11.0;
|
||||
// yellowness-blueness
|
||||
double b = (rA + gA - 2.0 * bA) / 9.0;
|
||||
|
||||
// auxiliary components
|
||||
double u = (20.0 * rA + 20.0 * gA + 21.0 * bA) / 20.0;
|
||||
double p2 = (40.0 * rA + 20.0 * gA + bA) / 20.0;
|
||||
|
||||
// hue
|
||||
double atan2 = Math.atan2(b, a);
|
||||
double atanDegrees = Math.toDegrees(atan2);
|
||||
double hue =
|
||||
atanDegrees < 0
|
||||
? atanDegrees + 360.0
|
||||
: atanDegrees >= 360 ? atanDegrees - 360.0 : atanDegrees;
|
||||
double hueRadians = Math.toRadians(hue);
|
||||
|
||||
// achromatic response to color
|
||||
double ac = p2 * viewingConditions.getNbb();
|
||||
|
||||
// CAM16 lightness and brightness
|
||||
double j =
|
||||
100.0
|
||||
* Math.pow(
|
||||
ac / viewingConditions.getAw(),
|
||||
viewingConditions.getC() * viewingConditions.getZ());
|
||||
double q =
|
||||
4.0
|
||||
/ viewingConditions.getC()
|
||||
* Math.sqrt(j / 100.0)
|
||||
* (viewingConditions.getAw() + 4.0)
|
||||
* viewingConditions.getFlRoot();
|
||||
|
||||
// CAM16 chroma, colorfulness, and saturation.
|
||||
double huePrime = (hue < 20.14) ? hue + 360 : hue;
|
||||
double eHue = 0.25 * (Math.cos(Math.toRadians(huePrime) + 2.0) + 3.8);
|
||||
double p1 = 50000.0 / 13.0 * eHue * viewingConditions.getNc() * viewingConditions.getNcb();
|
||||
double t = p1 * Math.hypot(a, b) / (u + 0.305);
|
||||
double alpha =
|
||||
Math.pow(1.64 - Math.pow(0.29, viewingConditions.getN()), 0.73) * Math.pow(t, 0.9);
|
||||
// CAM16 chroma, colorfulness, saturation
|
||||
double c = alpha * Math.sqrt(j / 100.0);
|
||||
double m = c * viewingConditions.getFlRoot();
|
||||
double s =
|
||||
50.0 * Math.sqrt(
|
||||
(alpha * viewingConditions.getC()) / (viewingConditions.getAw() + 4.0));
|
||||
|
||||
// CAM16-UCS components
|
||||
double jstar = (1.0 + 100.0 * 0.007) * j / (1.0 + 0.007 * j);
|
||||
double mstar = 1.0 / 0.0228 * Math.log1p(0.0228 * m);
|
||||
double astar = mstar * Math.cos(hueRadians);
|
||||
double bstar = mstar * Math.sin(hueRadians);
|
||||
|
||||
return new Cam16(hue, c, j, q, m, s, jstar, astar, bstar);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param j CAM16 lightness
|
||||
* @param c CAM16 chroma
|
||||
* @param h CAM16 hue
|
||||
*/
|
||||
static Cam16 fromJch(double j, double c, double h) {
|
||||
return fromJchInViewingConditions(j, c, h, ViewingConditions.DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param j CAM16 lightness
|
||||
* @param c CAM16 chroma
|
||||
* @param h CAM16 hue
|
||||
* @param viewingConditions Information about the environment where the color was observed.
|
||||
*/
|
||||
private static Cam16 fromJchInViewingConditions(
|
||||
double j, double c, double h, ViewingConditions viewingConditions) {
|
||||
double q =
|
||||
4.0
|
||||
/ viewingConditions.getC()
|
||||
* Math.sqrt(j / 100.0)
|
||||
* (viewingConditions.getAw() + 4.0)
|
||||
* viewingConditions.getFlRoot();
|
||||
double m = c * viewingConditions.getFlRoot();
|
||||
double alpha = c / Math.sqrt(j / 100.0);
|
||||
double s =
|
||||
50.0 * Math.sqrt(
|
||||
(alpha * viewingConditions.getC()) / (viewingConditions.getAw() + 4.0));
|
||||
|
||||
double hueRadians = Math.toRadians(h);
|
||||
double jstar = (1.0 + 100.0 * 0.007) * j / (1.0 + 0.007 * j);
|
||||
double mstar = 1.0 / 0.0228 * Math.log1p(0.0228 * m);
|
||||
double astar = mstar * Math.cos(hueRadians);
|
||||
double bstar = mstar * Math.sin(hueRadians);
|
||||
return new Cam16(h, c, j, q, m, s, jstar, astar, bstar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a CAM16 color from CAM16-UCS coordinates.
|
||||
*
|
||||
* @param jstar CAM16-UCS lightness.
|
||||
* @param astar CAM16-UCS a dimension. Like a* in L*a*b*, it is a Cartesian coordinate on the Y
|
||||
* axis.
|
||||
* @param bstar CAM16-UCS b dimension. Like a* in L*a*b*, it is a Cartesian coordinate on the X
|
||||
* axis.
|
||||
*/
|
||||
public static Cam16 fromUcs(double jstar, double astar, double bstar) {
|
||||
|
||||
return fromUcsInViewingConditions(jstar, astar, bstar, ViewingConditions.DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a CAM16 color from CAM16-UCS coordinates in defined viewing conditions.
|
||||
*
|
||||
* @param jstar CAM16-UCS lightness.
|
||||
* @param astar CAM16-UCS a dimension. Like a* in L*a*b*, it is a Cartesian
|
||||
* coordinate on the Y
|
||||
* axis.
|
||||
* @param bstar CAM16-UCS b dimension. Like a* in L*a*b*, it is a Cartesian
|
||||
* coordinate on the X
|
||||
* axis.
|
||||
* @param viewingConditions Information about the environment where the color was observed.
|
||||
*/
|
||||
public static Cam16 fromUcsInViewingConditions(
|
||||
double jstar, double astar, double bstar, ViewingConditions viewingConditions) {
|
||||
|
||||
double m = Math.hypot(astar, bstar);
|
||||
double m2 = Math.expm1(m * 0.0228) / 0.0228;
|
||||
double c = m2 / viewingConditions.getFlRoot();
|
||||
double h = Math.atan2(bstar, astar) * (180.0 / Math.PI);
|
||||
if (h < 0.0) {
|
||||
h += 360.0;
|
||||
}
|
||||
double j = jstar / (1. - (jstar - 100.) * 0.007);
|
||||
return fromJchInViewingConditions(j, c, h, viewingConditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* ARGB representation of the color. Assumes the color was viewed in default viewing conditions,
|
||||
* which are near-identical to the default viewing conditions for sRGB.
|
||||
*/
|
||||
public int toInt() {
|
||||
return viewed(ViewingConditions.DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* ARGB representation of the color, in defined viewing conditions.
|
||||
*
|
||||
* @param viewingConditions Information about the environment where the color will be viewed.
|
||||
* @return ARGB representation of color
|
||||
*/
|
||||
int viewed(ViewingConditions viewingConditions) {
|
||||
double[] xyz = xyzInViewingConditions(viewingConditions, tempArray);
|
||||
return ColorUtils.argbFromXyz(xyz[0], xyz[1], xyz[2]);
|
||||
}
|
||||
|
||||
double[] xyzInViewingConditions(ViewingConditions viewingConditions, double[] returnArray) {
|
||||
double alpha =
|
||||
(getChroma() == 0.0 || getJ() == 0.0) ? 0.0 : getChroma() / Math.sqrt(
|
||||
getJ() / 100.0);
|
||||
|
||||
double t =
|
||||
Math.pow(
|
||||
alpha / Math.pow(1.64 - Math.pow(0.29, viewingConditions.getN()), 0.73),
|
||||
1.0 / 0.9);
|
||||
double hRad = Math.toRadians(getHue());
|
||||
|
||||
double eHue = 0.25 * (Math.cos(hRad + 2.0) + 3.8);
|
||||
double ac =
|
||||
viewingConditions.getAw()
|
||||
* Math.pow(getJ() / 100.0,
|
||||
1.0 / viewingConditions.getC() / viewingConditions.getZ());
|
||||
double p1 =
|
||||
eHue * (50000.0 / 13.0) * viewingConditions.getNc() * viewingConditions.getNcb();
|
||||
double p2 = (ac / viewingConditions.getNbb());
|
||||
|
||||
double hSin = Math.sin(hRad);
|
||||
double hCos = Math.cos(hRad);
|
||||
|
||||
double gamma = 23.0 * (p2 + 0.305) * t / (23.0 * p1 + 11.0 * t * hCos + 108.0 * t * hSin);
|
||||
double a = gamma * hCos;
|
||||
double b = gamma * hSin;
|
||||
double rA = (460.0 * p2 + 451.0 * a + 288.0 * b) / 1403.0;
|
||||
double gA = (460.0 * p2 - 891.0 * a - 261.0 * b) / 1403.0;
|
||||
double bA = (460.0 * p2 - 220.0 * a - 6300.0 * b) / 1403.0;
|
||||
|
||||
double rCBase = max(0, (27.13 * Math.abs(rA)) / (400.0 - Math.abs(rA)));
|
||||
double rC =
|
||||
Math.signum(rA) * (100.0 / viewingConditions.getFl()) * Math.pow(rCBase,
|
||||
1.0 / 0.42);
|
||||
double gCBase = max(0, (27.13 * Math.abs(gA)) / (400.0 - Math.abs(gA)));
|
||||
double gC =
|
||||
Math.signum(gA) * (100.0 / viewingConditions.getFl()) * Math.pow(gCBase,
|
||||
1.0 / 0.42);
|
||||
double bCBase = max(0, (27.13 * Math.abs(bA)) / (400.0 - Math.abs(bA)));
|
||||
double bC =
|
||||
Math.signum(bA) * (100.0 / viewingConditions.getFl()) * Math.pow(bCBase,
|
||||
1.0 / 0.42);
|
||||
double rF = rC / viewingConditions.getRgbD()[0];
|
||||
double gF = gC / viewingConditions.getRgbD()[1];
|
||||
double bF = bC / viewingConditions.getRgbD()[2];
|
||||
|
||||
double[][] matrix = CAM16RGB_TO_XYZ;
|
||||
double x = (rF * matrix[0][0]) + (gF * matrix[0][1]) + (bF * matrix[0][2]);
|
||||
double y = (rF * matrix[1][0]) + (gF * matrix[1][1]) + (bF * matrix[1][2]);
|
||||
double z = (rF * matrix[2][0]) + (gF * matrix[2][1]) + (bF * matrix[2][2]);
|
||||
|
||||
if (returnArray != null) {
|
||||
returnArray[0] = x;
|
||||
returnArray[1] = y;
|
||||
returnArray[2] = z;
|
||||
return returnArray;
|
||||
} else {
|
||||
return new double[]{x, y, z};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.hct;
|
||||
|
||||
import com.android.systemui.monet.utils.ColorUtils;
|
||||
|
||||
/**
|
||||
* A color system built using CAM16 hue and chroma, and L* from L*a*b*.
|
||||
*
|
||||
* <p>Using L* creates a link between the color system, contrast, and thus accessibility. Contrast
|
||||
* ratio depends on relative luminance, or Y in the XYZ color space. L*, or perceptual luminance can
|
||||
* be calculated from Y.
|
||||
*
|
||||
* <p>Unlike Y, L* is linear to human perception, allowing trivial creation of accurate color tones.
|
||||
*
|
||||
* <p>Unlike contrast ratio, measuring contrast in L* is linear, and simple to calculate. A
|
||||
* difference of 40 in HCT tone guarantees a contrast ratio >= 3.0, and a difference of 50
|
||||
* guarantees a contrast ratio >= 4.5.
|
||||
*/
|
||||
|
||||
/**
|
||||
* HCT, hue, chroma, and tone. A color system that provides a perceptually accurate color
|
||||
* measurement system that can also accurately render what colors will appear as in different
|
||||
* lighting environments.
|
||||
*/
|
||||
public final class Hct {
|
||||
private double hue;
|
||||
private double chroma;
|
||||
private double tone;
|
||||
private int argb;
|
||||
|
||||
/**
|
||||
* Create an HCT color from hue, chroma, and tone.
|
||||
*
|
||||
* @param hue 0 <= hue < 360; invalid values are corrected.
|
||||
* @param chroma 0 <= chroma < ?; Informally, colorfulness. The color returned may be lower
|
||||
* than
|
||||
* the requested chroma. Chroma has a different maximum for any given hue and
|
||||
* tone.
|
||||
* @param tone 0 <= tone <= 100; invalid values are corrected.
|
||||
* @return HCT representation of a color in default viewing conditions.
|
||||
*/
|
||||
public static Hct from(double hue, double chroma, double tone) {
|
||||
int argb = HctSolver.solveToInt(hue, chroma, tone);
|
||||
return new Hct(argb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an HCT color from a color.
|
||||
*
|
||||
* @param argb ARGB representation of a color.
|
||||
* @return HCT representation of a color in default viewing conditions
|
||||
*/
|
||||
public static Hct fromInt(int argb) {
|
||||
return new Hct(argb);
|
||||
}
|
||||
|
||||
private Hct(int argb) {
|
||||
setInternalState(argb);
|
||||
}
|
||||
|
||||
public double getHue() {
|
||||
return hue;
|
||||
}
|
||||
|
||||
public double getChroma() {
|
||||
return chroma;
|
||||
}
|
||||
|
||||
public double getTone() {
|
||||
return tone;
|
||||
}
|
||||
|
||||
/** This color converted to ARGB, i.e. a hex code. */
|
||||
public int toInt() {
|
||||
return argb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the hue of this color. Chroma may decrease because chroma has a different maximum for any
|
||||
* given hue and tone.
|
||||
*
|
||||
* @param newHue 0 <= newHue < 360; invalid values are corrected.
|
||||
*/
|
||||
public void setHue(double newHue) {
|
||||
setInternalState(HctSolver.solveToInt(newHue, chroma, tone));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the chroma of this color. Chroma may decrease because chroma has a different maximum for
|
||||
* any given hue and tone.
|
||||
*
|
||||
* @param newChroma 0 <= newChroma < ?
|
||||
*/
|
||||
public void setChroma(double newChroma) {
|
||||
setInternalState(HctSolver.solveToInt(hue, newChroma, tone));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tone of this color. Chroma may decrease because chroma has a different maximum for
|
||||
* any
|
||||
* given hue and tone.
|
||||
*
|
||||
* @param newTone 0 <= newTone <= 100; invalid valids are corrected.
|
||||
*/
|
||||
public void setTone(double newTone) {
|
||||
setInternalState(HctSolver.solveToInt(hue, chroma, newTone));
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate a color into different ViewingConditions.
|
||||
*
|
||||
* <p>Colors change appearance. They look different with lights on versus off, the same color,
|
||||
* as
|
||||
* in hex code, on white looks different when on black. This is called color relativity, most
|
||||
* famously explicated by Josef Albers in Interaction of Color.
|
||||
*
|
||||
* <p>In color science, color appearance models can account for this and calculate the
|
||||
* appearance
|
||||
* of a color in different settings. HCT is based on CAM16, a color appearance model, and uses
|
||||
* it
|
||||
* to make these calculations.
|
||||
*
|
||||
* <p>See ViewingConditions.make for parameters affecting color appearance.
|
||||
*/
|
||||
public Hct inViewingConditions(ViewingConditions vc) {
|
||||
// 1. Use CAM16 to find XYZ coordinates of color in specified VC.
|
||||
Cam16 cam16 = Cam16.fromInt(toInt());
|
||||
double[] viewedInVc = cam16.xyzInViewingConditions(vc, null);
|
||||
|
||||
// 2. Create CAM16 of those XYZ coordinates in default VC.
|
||||
Cam16 recastInVc =
|
||||
Cam16.fromXyzInViewingConditions(
|
||||
viewedInVc[0], viewedInVc[1], viewedInVc[2], ViewingConditions.DEFAULT);
|
||||
|
||||
// 3. Create HCT from:
|
||||
// - CAM16 using default VC with XYZ coordinates in specified VC.
|
||||
// - L* converted from Y in XYZ coordinates in specified VC.
|
||||
return Hct.from(
|
||||
recastInVc.getHue(), recastInVc.getChroma(), ColorUtils.lstarFromY(viewedInVc[1]));
|
||||
}
|
||||
|
||||
private void setInternalState(int argb) {
|
||||
this.argb = argb;
|
||||
Cam16 cam = Cam16.fromInt(argb);
|
||||
hue = cam.getHue();
|
||||
chroma = cam.getChroma();
|
||||
this.tone = ColorUtils.lstarFromArgb(argb);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,677 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.hct;
|
||||
|
||||
import com.android.systemui.monet.utils.ColorUtils;
|
||||
import com.android.systemui.monet.utils.MathUtils;
|
||||
|
||||
/** A class that solves the HCT equation. */
|
||||
public class HctSolver {
|
||||
private HctSolver() {
|
||||
}
|
||||
|
||||
static final double[][] SCALED_DISCOUNT_FROM_LINRGB =
|
||||
new double[][]{
|
||||
new double[]{
|
||||
0.001200833568784504, 0.002389694492170889, 0.0002795742885861124,
|
||||
},
|
||||
new double[]{
|
||||
0.0005891086651375999, 0.0029785502573438758, 0.0003270666104008398,
|
||||
},
|
||||
new double[]{
|
||||
0.00010146692491640572, 0.0005364214359186694, 0.0032979401770712076,
|
||||
},
|
||||
};
|
||||
|
||||
static final double[][] LINRGB_FROM_SCALED_DISCOUNT =
|
||||
new double[][]{
|
||||
new double[]{
|
||||
1373.2198709594231, -1100.4251190754821, -7.278681089101213,
|
||||
},
|
||||
new double[]{
|
||||
-271.815969077903, 559.6580465940733, -32.46047482791194,
|
||||
},
|
||||
new double[]{
|
||||
1.9622899599665666, -57.173814538844006, 308.7233197812385,
|
||||
},
|
||||
};
|
||||
|
||||
static final double[] Y_FROM_LINRGB = new double[]{0.2126, 0.7152, 0.0722};
|
||||
|
||||
static final double[] CRITICAL_PLANES =
|
||||
new double[]{
|
||||
0.015176349177441876,
|
||||
0.045529047532325624,
|
||||
0.07588174588720938,
|
||||
0.10623444424209313,
|
||||
0.13658714259697685,
|
||||
0.16693984095186062,
|
||||
0.19729253930674434,
|
||||
0.2276452376616281,
|
||||
0.2579979360165119,
|
||||
0.28835063437139563,
|
||||
0.3188300904430532,
|
||||
0.350925934958123,
|
||||
0.3848314933096426,
|
||||
0.42057480301049466,
|
||||
0.458183274052838,
|
||||
0.4976837250274023,
|
||||
0.5391024159806381,
|
||||
0.5824650784040898,
|
||||
0.6277969426914107,
|
||||
0.6751227633498623,
|
||||
0.7244668422128921,
|
||||
0.775853049866786,
|
||||
0.829304845476233,
|
||||
0.8848452951698498,
|
||||
0.942497089126609,
|
||||
1.0022825574869039,
|
||||
1.0642236851973577,
|
||||
1.1283421258858297,
|
||||
1.1946592148522128,
|
||||
1.2631959812511864,
|
||||
1.3339731595349034,
|
||||
1.407011200216447,
|
||||
1.4823302800086415,
|
||||
1.5599503113873272,
|
||||
1.6398909516233677,
|
||||
1.7221716113234105,
|
||||
1.8068114625156377,
|
||||
1.8938294463134073,
|
||||
1.9832442801866852,
|
||||
2.075074464868551,
|
||||
2.1693382909216234,
|
||||
2.2660538449872063,
|
||||
2.36523901573795,
|
||||
2.4669114995532007,
|
||||
2.5710888059345764,
|
||||
2.6777882626779785,
|
||||
2.7870270208169257,
|
||||
2.898822059350997,
|
||||
3.0131901897720907,
|
||||
3.1301480604002863,
|
||||
3.2497121605402226,
|
||||
3.3718988244681087,
|
||||
3.4967242352587946,
|
||||
3.624204428461639,
|
||||
3.754355295633311,
|
||||
3.887192587735158,
|
||||
4.022731918402185,
|
||||
4.160988767090289,
|
||||
4.301978482107941,
|
||||
4.445716283538092,
|
||||
4.592217266055746,
|
||||
4.741496401646282,
|
||||
4.893568542229298,
|
||||
5.048448422192488,
|
||||
5.20615066083972,
|
||||
5.3666897647573375,
|
||||
5.5300801301023865,
|
||||
5.696336044816294,
|
||||
5.865471690767354,
|
||||
6.037501145825082,
|
||||
6.212438385869475,
|
||||
6.390297286737924,
|
||||
6.571091626112461,
|
||||
6.7548350853498045,
|
||||
6.941541251256611,
|
||||
7.131223617812143,
|
||||
7.323895587840543,
|
||||
7.5195704746346665,
|
||||
7.7182615035334345,
|
||||
7.919981813454504,
|
||||
8.124744458384042,
|
||||
8.332562408825165,
|
||||
8.543448553206703,
|
||||
8.757415699253682,
|
||||
8.974476575321063,
|
||||
9.194643831691977,
|
||||
9.417930041841839,
|
||||
9.644347703669503,
|
||||
9.873909240696694,
|
||||
10.106627003236781,
|
||||
10.342513269534024,
|
||||
10.58158024687427,
|
||||
10.8238400726681,
|
||||
11.069304815507364,
|
||||
11.317986476196008,
|
||||
11.569896988756009,
|
||||
11.825048221409341,
|
||||
12.083451977536606,
|
||||
12.345119996613247,
|
||||
12.610063955123938,
|
||||
12.878295467455942,
|
||||
13.149826086772048,
|
||||
13.42466730586372,
|
||||
13.702830557985108,
|
||||
13.984327217668513,
|
||||
14.269168601521828,
|
||||
14.55736596900856,
|
||||
14.848930523210871,
|
||||
15.143873411576273,
|
||||
15.44220572664832,
|
||||
15.743938506781891,
|
||||
16.04908273684337,
|
||||
16.35764934889634,
|
||||
16.66964922287304,
|
||||
16.985093187232053,
|
||||
17.30399201960269,
|
||||
17.62635644741625,
|
||||
17.95219714852476,
|
||||
18.281524751807332,
|
||||
18.614349837764564,
|
||||
18.95068293910138,
|
||||
19.290534541298456,
|
||||
19.633915083172692,
|
||||
19.98083495742689,
|
||||
20.331304511189067,
|
||||
20.685334046541502,
|
||||
21.042933821039977,
|
||||
21.404114048223256,
|
||||
21.76888489811322,
|
||||
22.137256497705877,
|
||||
22.50923893145328,
|
||||
22.884842241736916,
|
||||
23.264076429332462,
|
||||
23.6469514538663,
|
||||
24.033477234264016,
|
||||
24.42366364919083,
|
||||
24.817520537484558,
|
||||
25.21505769858089,
|
||||
25.61628489293138,
|
||||
26.021211842414342,
|
||||
26.429848230738664,
|
||||
26.842203703840827,
|
||||
27.258287870275353,
|
||||
27.678110301598522,
|
||||
28.10168053274597,
|
||||
28.529008062403893,
|
||||
28.96010235337422,
|
||||
29.39497283293396,
|
||||
29.83362889318845,
|
||||
30.276079891419332,
|
||||
30.722335150426627,
|
||||
31.172403958865512,
|
||||
31.62629557157785,
|
||||
32.08401920991837,
|
||||
32.54558406207592,
|
||||
33.010999283389665,
|
||||
33.4802739966603,
|
||||
33.953417292456834,
|
||||
34.430438229418264,
|
||||
34.911345834551085,
|
||||
35.39614910352207,
|
||||
35.88485700094671,
|
||||
36.37747846067349,
|
||||
36.87402238606382,
|
||||
37.37449765026789,
|
||||
37.87891309649659,
|
||||
38.38727753828926,
|
||||
38.89959975977785,
|
||||
39.41588851594697,
|
||||
39.93615253289054,
|
||||
40.460400508064545,
|
||||
40.98864111053629,
|
||||
41.520882981230194,
|
||||
42.05713473317016,
|
||||
42.597404951718396,
|
||||
43.141702194811224,
|
||||
43.6900349931913,
|
||||
44.24241185063697,
|
||||
44.798841244188324,
|
||||
45.35933162437017,
|
||||
45.92389141541209,
|
||||
46.49252901546552,
|
||||
47.065252796817916,
|
||||
47.64207110610409,
|
||||
48.22299226451468,
|
||||
48.808024568002054,
|
||||
49.3971762874833,
|
||||
49.9904556690408,
|
||||
50.587870934119984,
|
||||
51.189430279724725,
|
||||
51.79514187861014,
|
||||
52.40501387947288,
|
||||
53.0190544071392,
|
||||
53.637271562750364,
|
||||
54.259673423945976,
|
||||
54.88626804504493,
|
||||
55.517063457223934,
|
||||
56.15206766869424,
|
||||
56.79128866487574,
|
||||
57.43473440856916,
|
||||
58.08241284012621,
|
||||
58.734331877617365,
|
||||
59.39049941699807,
|
||||
60.05092333227251,
|
||||
60.715611475655585,
|
||||
61.38457167773311,
|
||||
62.057811747619894,
|
||||
62.7353394731159,
|
||||
63.417162620860914,
|
||||
64.10328893648692,
|
||||
64.79372614476921,
|
||||
65.48848194977529,
|
||||
66.18756403501224,
|
||||
66.89098006357258,
|
||||
67.59873767827808,
|
||||
68.31084450182222,
|
||||
69.02730813691093,
|
||||
69.74813616640164,
|
||||
70.47333615344107,
|
||||
71.20291564160104,
|
||||
71.93688215501312,
|
||||
72.67524319850172,
|
||||
73.41800625771542,
|
||||
74.16517879925733,
|
||||
74.9167682708136,
|
||||
75.67278210128072,
|
||||
76.43322770089146,
|
||||
77.1981124613393,
|
||||
77.96744375590167,
|
||||
78.74122893956174,
|
||||
79.51947534912904,
|
||||
80.30219030335869,
|
||||
81.08938110306934,
|
||||
81.88105503125999,
|
||||
82.67721935322541,
|
||||
83.4778813166706,
|
||||
84.28304815182372,
|
||||
85.09272707154808,
|
||||
85.90692527145302,
|
||||
86.72564993000343,
|
||||
87.54890820862819,
|
||||
88.3767072518277,
|
||||
89.2090541872801,
|
||||
90.04595612594655,
|
||||
90.88742016217518,
|
||||
91.73345337380438,
|
||||
92.58406282226491,
|
||||
93.43925555268066,
|
||||
94.29903859396902,
|
||||
95.16341895893969,
|
||||
96.03240364439274,
|
||||
96.9059996312159,
|
||||
97.78421388448044,
|
||||
98.6670533535366,
|
||||
99.55452497210776,
|
||||
};
|
||||
|
||||
/**
|
||||
* Sanitizes a small enough angle in radians.
|
||||
*
|
||||
* @param angle An angle in radians; must not deviate too much from 0.
|
||||
* @return A coterminal angle between 0 and 2pi.
|
||||
*/
|
||||
static double sanitizeRadians(double angle) {
|
||||
return (angle + Math.PI * 8) % (Math.PI * 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delinearizes an RGB component, returning a floating-point number.
|
||||
*
|
||||
* @param rgbComponent 0.0 <= rgb_component <= 100.0, represents linear R/G/B channel
|
||||
* @return 0.0 <= output <= 255.0, color channel converted to regular RGB space
|
||||
*/
|
||||
static double trueDelinearized(double rgbComponent) {
|
||||
double normalized = rgbComponent / 100.0;
|
||||
double delinearized = 0.0;
|
||||
if (normalized <= 0.0031308) {
|
||||
delinearized = normalized * 12.92;
|
||||
} else {
|
||||
delinearized = 1.055 * Math.pow(normalized, 1.0 / 2.4) - 0.055;
|
||||
}
|
||||
return delinearized * 255.0;
|
||||
}
|
||||
|
||||
static double chromaticAdaptation(double component) {
|
||||
double af = Math.pow(Math.abs(component), 0.42);
|
||||
return MathUtils.signum(component) * 400.0 * af / (af + 27.13);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hue of a linear RGB color in CAM16.
|
||||
*
|
||||
* @param linrgb The linear RGB coordinates of a color.
|
||||
* @return The hue of the color in CAM16, in radians.
|
||||
*/
|
||||
static double hueOf(double[] linrgb) {
|
||||
double[] scaledDiscount = MathUtils.matrixMultiply(linrgb, SCALED_DISCOUNT_FROM_LINRGB);
|
||||
double rA = chromaticAdaptation(scaledDiscount[0]);
|
||||
double gA = chromaticAdaptation(scaledDiscount[1]);
|
||||
double bA = chromaticAdaptation(scaledDiscount[2]);
|
||||
// redness-greenness
|
||||
double a = (11.0 * rA + -12.0 * gA + bA) / 11.0;
|
||||
// yellowness-blueness
|
||||
double b = (rA + gA - 2.0 * bA) / 9.0;
|
||||
return Math.atan2(b, a);
|
||||
}
|
||||
|
||||
static boolean areInCyclicOrder(double a, double b, double c) {
|
||||
double deltaAB = sanitizeRadians(b - a);
|
||||
double deltaAC = sanitizeRadians(c - a);
|
||||
return deltaAB < deltaAC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Solves the lerp equation.
|
||||
*
|
||||
* @param source The starting number.
|
||||
* @param mid The number in the middle.
|
||||
* @param target The ending number.
|
||||
* @return A number t such that lerp(source, target, t) = mid.
|
||||
*/
|
||||
static double intercept(double source, double mid, double target) {
|
||||
return (mid - source) / (target - source);
|
||||
}
|
||||
|
||||
static double[] lerpPoint(double[] source, double t, double[] target) {
|
||||
return new double[]{
|
||||
source[0] + (target[0] - source[0]) * t,
|
||||
source[1] + (target[1] - source[1]) * t,
|
||||
source[2] + (target[2] - source[2]) * t,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Intersects a segment with a plane.
|
||||
*
|
||||
* @param source The coordinates of point A.
|
||||
* @param coordinate The R-, G-, or B-coordinate of the plane.
|
||||
* @param target The coordinates of point B.
|
||||
* @param axis The axis the plane is perpendicular with. (0: R, 1: G, 2: B)
|
||||
* @return The intersection point of the segment AB with the plane R=coordinate,
|
||||
* G=coordinate, or
|
||||
* B=coordinate
|
||||
*/
|
||||
static double[] setCoordinate(double[] source, double coordinate, double[] target, int axis) {
|
||||
double t = intercept(source[axis], coordinate, target[axis]);
|
||||
return lerpPoint(source, t, target);
|
||||
}
|
||||
|
||||
static boolean isBounded(double x) {
|
||||
return 0.0 <= x && x <= 100.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nth possible vertex of the polygonal intersection.
|
||||
*
|
||||
* @param y The Y value of the plane.
|
||||
* @param n The zero-based index of the point. 0 <= n <= 11.
|
||||
* @return The nth possible vertex of the polygonal intersection of the y plane and the RGB
|
||||
* cube,
|
||||
* in linear RGB coordinates, if it exists. If this possible vertex lies outside of the cube,
|
||||
* [-1.0, -1.0, -1.0] is returned.
|
||||
*/
|
||||
static double[] nthVertex(double y, int n) {
|
||||
double kR = Y_FROM_LINRGB[0];
|
||||
double kG = Y_FROM_LINRGB[1];
|
||||
double kB = Y_FROM_LINRGB[2];
|
||||
double coordA = n % 4 <= 1 ? 0.0 : 100.0;
|
||||
double coordB = n % 2 == 0 ? 0.0 : 100.0;
|
||||
if (n < 4) {
|
||||
double g = coordA;
|
||||
double b = coordB;
|
||||
double r = (y - g * kG - b * kB) / kR;
|
||||
if (isBounded(r)) {
|
||||
return new double[]{r, g, b};
|
||||
} else {
|
||||
return new double[]{-1.0, -1.0, -1.0};
|
||||
}
|
||||
} else if (n < 8) {
|
||||
double b = coordA;
|
||||
double r = coordB;
|
||||
double g = (y - r * kR - b * kB) / kG;
|
||||
if (isBounded(g)) {
|
||||
return new double[]{r, g, b};
|
||||
} else {
|
||||
return new double[]{-1.0, -1.0, -1.0};
|
||||
}
|
||||
} else {
|
||||
double r = coordA;
|
||||
double g = coordB;
|
||||
double b = (y - r * kR - g * kG) / kB;
|
||||
if (isBounded(b)) {
|
||||
return new double[]{r, g, b};
|
||||
} else {
|
||||
return new double[]{-1.0, -1.0, -1.0};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the segment containing the desired color.
|
||||
*
|
||||
* @param y The Y value of the color.
|
||||
* @param targetHue The hue of the color.
|
||||
* @return A list of two sets of linear RGB coordinates, each corresponding to an endpoint of
|
||||
* the
|
||||
* segment containing the desired color.
|
||||
*/
|
||||
static double[][] bisectToSegment(double y, double targetHue) {
|
||||
double[] left = new double[]{-1.0, -1.0, -1.0};
|
||||
double[] right = left;
|
||||
double leftHue = 0.0;
|
||||
double rightHue = 0.0;
|
||||
boolean initialized = false;
|
||||
boolean uncut = true;
|
||||
for (int n = 0; n < 12; n++) {
|
||||
double[] mid = nthVertex(y, n);
|
||||
if (mid[0] < 0) {
|
||||
continue;
|
||||
}
|
||||
double midHue = hueOf(mid);
|
||||
if (!initialized) {
|
||||
left = mid;
|
||||
right = mid;
|
||||
leftHue = midHue;
|
||||
rightHue = midHue;
|
||||
initialized = true;
|
||||
continue;
|
||||
}
|
||||
if (uncut || areInCyclicOrder(leftHue, midHue, rightHue)) {
|
||||
uncut = false;
|
||||
if (areInCyclicOrder(leftHue, targetHue, midHue)) {
|
||||
right = mid;
|
||||
rightHue = midHue;
|
||||
} else {
|
||||
left = mid;
|
||||
leftHue = midHue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new double[][]{left, right};
|
||||
}
|
||||
|
||||
static double[] midpoint(double[] a, double[] b) {
|
||||
return new double[]{
|
||||
(a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2,
|
||||
};
|
||||
}
|
||||
|
||||
static int criticalPlaneBelow(double x) {
|
||||
return (int) Math.floor(x - 0.5);
|
||||
}
|
||||
|
||||
static int criticalPlaneAbove(double x) {
|
||||
return (int) Math.ceil(x - 0.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a color with the given Y and hue on the boundary of the cube.
|
||||
*
|
||||
* @param y The Y value of the color.
|
||||
* @param targetHue The hue of the color.
|
||||
* @return The desired color, in linear RGB coordinates.
|
||||
*/
|
||||
static double[] bisectToLimit(double y, double targetHue) {
|
||||
double[][] segment = bisectToSegment(y, targetHue);
|
||||
double[] left = segment[0];
|
||||
double leftHue = hueOf(left);
|
||||
double[] right = segment[1];
|
||||
for (int axis = 0; axis < 3; axis++) {
|
||||
if (left[axis] != right[axis]) {
|
||||
int lPlane = -1;
|
||||
int rPlane = 255;
|
||||
if (left[axis] < right[axis]) {
|
||||
lPlane = criticalPlaneBelow(trueDelinearized(left[axis]));
|
||||
rPlane = criticalPlaneAbove(trueDelinearized(right[axis]));
|
||||
} else {
|
||||
lPlane = criticalPlaneAbove(trueDelinearized(left[axis]));
|
||||
rPlane = criticalPlaneBelow(trueDelinearized(right[axis]));
|
||||
}
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (Math.abs(rPlane - lPlane) <= 1) {
|
||||
break;
|
||||
} else {
|
||||
int mPlane = (int) Math.floor((lPlane + rPlane) / 2.0);
|
||||
double midPlaneCoordinate = CRITICAL_PLANES[mPlane];
|
||||
double[] mid = setCoordinate(left, midPlaneCoordinate, right, axis);
|
||||
double midHue = hueOf(mid);
|
||||
if (areInCyclicOrder(leftHue, targetHue, midHue)) {
|
||||
right = mid;
|
||||
rPlane = mPlane;
|
||||
} else {
|
||||
left = mid;
|
||||
leftHue = midHue;
|
||||
lPlane = mPlane;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return midpoint(left, right);
|
||||
}
|
||||
|
||||
static double inverseChromaticAdaptation(double adapted) {
|
||||
double adaptedAbs = Math.abs(adapted);
|
||||
double base = Math.max(0, 27.13 * adaptedAbs / (400.0 - adaptedAbs));
|
||||
return MathUtils.signum(adapted) * Math.pow(base, 1.0 / 0.42);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a color with the given hue, chroma, and Y.
|
||||
*
|
||||
* @param hueRadians The desired hue in radians.
|
||||
* @param chroma The desired chroma.
|
||||
* @param y The desired Y.
|
||||
* @return The desired color as a hexadecimal integer, if found; 0 otherwise.
|
||||
*/
|
||||
static int findResultByJ(double hueRadians, double chroma, double y) {
|
||||
// Initial estimate of j.
|
||||
double j = Math.sqrt(y) * 11.0;
|
||||
// ===========================================================
|
||||
// Operations inlined from Cam16 to avoid repeated calculation
|
||||
// ===========================================================
|
||||
ViewingConditions viewingConditions = ViewingConditions.DEFAULT;
|
||||
double tInnerCoeff = 1 / Math.pow(1.64 - Math.pow(0.29, viewingConditions.getN()), 0.73);
|
||||
double eHue = 0.25 * (Math.cos(hueRadians + 2.0) + 3.8);
|
||||
double p1 =
|
||||
eHue * (50000.0 / 13.0) * viewingConditions.getNc() * viewingConditions.getNcb();
|
||||
double hSin = Math.sin(hueRadians);
|
||||
double hCos = Math.cos(hueRadians);
|
||||
for (int iterationRound = 0; iterationRound < 5; iterationRound++) {
|
||||
// ===========================================================
|
||||
// Operations inlined from Cam16 to avoid repeated calculation
|
||||
// ===========================================================
|
||||
double jNormalized = j / 100.0;
|
||||
double alpha = chroma == 0.0 || j == 0.0 ? 0.0 : chroma / Math.sqrt(jNormalized);
|
||||
double t = Math.pow(alpha * tInnerCoeff, 1.0 / 0.9);
|
||||
double ac =
|
||||
viewingConditions.getAw()
|
||||
* Math.pow(jNormalized,
|
||||
1.0 / viewingConditions.getC() / viewingConditions.getZ());
|
||||
double p2 = ac / viewingConditions.getNbb();
|
||||
double gamma = 23.0 * (p2 + 0.305) * t / (23.0 * p1 + 11 * t * hCos + 108.0 * t * hSin);
|
||||
double a = gamma * hCos;
|
||||
double b = gamma * hSin;
|
||||
double rA = (460.0 * p2 + 451.0 * a + 288.0 * b) / 1403.0;
|
||||
double gA = (460.0 * p2 - 891.0 * a - 261.0 * b) / 1403.0;
|
||||
double bA = (460.0 * p2 - 220.0 * a - 6300.0 * b) / 1403.0;
|
||||
double rCScaled = inverseChromaticAdaptation(rA);
|
||||
double gCScaled = inverseChromaticAdaptation(gA);
|
||||
double bCScaled = inverseChromaticAdaptation(bA);
|
||||
double[] linrgb =
|
||||
MathUtils.matrixMultiply(
|
||||
new double[]{rCScaled, gCScaled, bCScaled},
|
||||
LINRGB_FROM_SCALED_DISCOUNT);
|
||||
// ===========================================================
|
||||
// Operations inlined from Cam16 to avoid repeated calculation
|
||||
// ===========================================================
|
||||
if (linrgb[0] < 0 || linrgb[1] < 0 || linrgb[2] < 0) {
|
||||
return 0;
|
||||
}
|
||||
double kR = Y_FROM_LINRGB[0];
|
||||
double kG = Y_FROM_LINRGB[1];
|
||||
double kB = Y_FROM_LINRGB[2];
|
||||
double fnj = kR * linrgb[0] + kG * linrgb[1] + kB * linrgb[2];
|
||||
if (fnj <= 0) {
|
||||
return 0;
|
||||
}
|
||||
if (iterationRound == 4 || Math.abs(fnj - y) < 0.002) {
|
||||
if (linrgb[0] > 100.01 || linrgb[1] > 100.01 || linrgb[2] > 100.01) {
|
||||
return 0;
|
||||
}
|
||||
return ColorUtils.argbFromLinrgb(linrgb);
|
||||
}
|
||||
// Iterates with Newton method,
|
||||
// Using 2 * fn(j) / j as the approximation of fn'(j)
|
||||
j = j - (fnj - y) * j / (2 * fnj);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds an sRGB color with the given hue, chroma, and L*, if possible.
|
||||
*
|
||||
* @param hueDegrees The desired hue, in degrees.
|
||||
* @param chroma The desired chroma.
|
||||
* @param lstar The desired L*.
|
||||
* @return A hexadecimal representing the sRGB color. The color has sufficiently close hue,
|
||||
* chroma, and L* to the desired values, if possible; otherwise, the hue and L* will be
|
||||
* sufficiently close, and chroma will be maximized.
|
||||
*/
|
||||
public static int solveToInt(double hueDegrees, double chroma, double lstar) {
|
||||
if (chroma < 0.0001 || lstar < 0.0001 || lstar > 99.9999) {
|
||||
return ColorUtils.argbFromLstar(lstar);
|
||||
}
|
||||
hueDegrees = MathUtils.sanitizeDegreesDouble(hueDegrees);
|
||||
double hueRadians = hueDegrees / 180 * Math.PI;
|
||||
double y = ColorUtils.yFromLstar(lstar);
|
||||
int exactAnswer = findResultByJ(hueRadians, chroma, y);
|
||||
if (exactAnswer != 0) {
|
||||
return exactAnswer;
|
||||
}
|
||||
double[] linrgb = bisectToLimit(y, hueRadians);
|
||||
return ColorUtils.argbFromLinrgb(linrgb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds an sRGB color with the given hue, chroma, and L*, if possible.
|
||||
*
|
||||
* @param hueDegrees The desired hue, in degrees.
|
||||
* @param chroma The desired chroma.
|
||||
* @param lstar The desired L*.
|
||||
* @return A CAM16 object representing the sRGB color. The color has sufficiently close hue,
|
||||
* chroma, and L* to the desired values, if possible; otherwise, the hue and L* will be
|
||||
* sufficiently close, and chroma will be maximized.
|
||||
*/
|
||||
public static Cam16 solveToCam(double hueDegrees, double chroma, double lstar) {
|
||||
return Cam16.fromInt(solveToInt(hueDegrees, chroma, lstar));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.hct;
|
||||
|
||||
import com.android.systemui.monet.utils.ColorUtils;
|
||||
import com.android.systemui.monet.utils.MathUtils;
|
||||
|
||||
/**
|
||||
* In traditional color spaces, a color can be identified solely by the observer's measurement of
|
||||
* the color. Color appearance models such as CAM16 also use information about the environment where
|
||||
* the color was observed, known as the viewing conditions.
|
||||
*
|
||||
* <p>For example, white under the traditional assumption of a midday sun white point is accurately
|
||||
* measured as a slightly chromatic blue by CAM16. (roughly, hue 203, chroma 3, lightness 100)
|
||||
*
|
||||
* <p>This class caches intermediate values of the CAM16 conversion process that depend only on
|
||||
* viewing conditions, enabling speed ups.
|
||||
*/
|
||||
public final class ViewingConditions {
|
||||
/** sRGB-like viewing conditions. */
|
||||
public static final ViewingConditions DEFAULT =
|
||||
ViewingConditions.defaultWithBackgroundLstar(50.0);
|
||||
|
||||
private final double aw;
|
||||
private final double nbb;
|
||||
private final double ncb;
|
||||
private final double c;
|
||||
private final double nc;
|
||||
private final double n;
|
||||
private final double[] rgbD;
|
||||
private final double fl;
|
||||
private final double flRoot;
|
||||
private final double z;
|
||||
|
||||
public double getAw() {
|
||||
return aw;
|
||||
}
|
||||
|
||||
public double getN() {
|
||||
return n;
|
||||
}
|
||||
|
||||
public double getNbb() {
|
||||
return nbb;
|
||||
}
|
||||
|
||||
double getNcb() {
|
||||
return ncb;
|
||||
}
|
||||
|
||||
double getC() {
|
||||
return c;
|
||||
}
|
||||
|
||||
double getNc() {
|
||||
return nc;
|
||||
}
|
||||
|
||||
public double[] getRgbD() {
|
||||
return rgbD;
|
||||
}
|
||||
|
||||
double getFl() {
|
||||
return fl;
|
||||
}
|
||||
|
||||
public double getFlRoot() {
|
||||
return flRoot;
|
||||
}
|
||||
|
||||
double getZ() {
|
||||
return z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create ViewingConditions from a simple, physically relevant, set of parameters.
|
||||
*
|
||||
* @param whitePoint White point, measured in the XYZ color space. default = D65, or
|
||||
* sunny day
|
||||
* afternoon
|
||||
* @param adaptingLuminance The luminance of the adapting field. Informally, how bright it
|
||||
* is in
|
||||
* the room where the color is viewed. Can be calculated from lux
|
||||
* by multiplying lux by
|
||||
* 0.0586. default = 11.72, or 200 lux.
|
||||
* @param backgroundLstar The lightness of the area surrounding the color. measured by L*
|
||||
* in
|
||||
* L*a*b*. default = 50.0
|
||||
* @param surround A general description of the lighting surrounding the color. 0
|
||||
* is pitch dark,
|
||||
* like watching a movie in a theater. 1.0 is a dimly light room,
|
||||
* like watching TV at home at
|
||||
* night. 2.0 means there is no difference between the lighting on
|
||||
* the color and around it.
|
||||
* default = 2.0
|
||||
* @param discountingIlluminant Whether the eye accounts for the tint of the ambient lighting,
|
||||
* such as knowing an apple is still red in green light. default =
|
||||
* false, the eye does not
|
||||
* perform this process on self-luminous objects like displays.
|
||||
*/
|
||||
public static ViewingConditions make(
|
||||
double[] whitePoint,
|
||||
double adaptingLuminance,
|
||||
double backgroundLstar,
|
||||
double surround,
|
||||
boolean discountingIlluminant) {
|
||||
// A background of pure black is non-physical and leads to infinities that represent the
|
||||
// idea
|
||||
// that any color viewed in pure black can't be seen.
|
||||
backgroundLstar = Math.max(0.1, backgroundLstar);
|
||||
// Transform white point XYZ to 'cone'/'rgb' responses
|
||||
double[][] matrix = Cam16.XYZ_TO_CAM16RGB;
|
||||
double[] xyz = whitePoint;
|
||||
double rW = (xyz[0] * matrix[0][0]) + (xyz[1] * matrix[0][1]) + (xyz[2] * matrix[0][2]);
|
||||
double gW = (xyz[0] * matrix[1][0]) + (xyz[1] * matrix[1][1]) + (xyz[2] * matrix[1][2]);
|
||||
double bW = (xyz[0] * matrix[2][0]) + (xyz[1] * matrix[2][1]) + (xyz[2] * matrix[2][2]);
|
||||
double f = 0.8 + (surround / 10.0);
|
||||
double c =
|
||||
(f >= 0.9)
|
||||
? MathUtils.lerp(0.59, 0.69, ((f - 0.9) * 10.0))
|
||||
: MathUtils.lerp(0.525, 0.59, ((f - 0.8) * 10.0));
|
||||
double d =
|
||||
discountingIlluminant
|
||||
? 1.0
|
||||
: f * (1.0 - ((1.0 / 3.6) * Math.exp((-adaptingLuminance - 42.0) / 92.0)));
|
||||
d = MathUtils.clampDouble(0.0, 1.0, d);
|
||||
double nc = f;
|
||||
double[] rgbD =
|
||||
new double[]{
|
||||
d * (100.0 / rW) + 1.0 - d, d * (100.0 / gW) + 1.0 - d,
|
||||
d * (100.0 / bW) + 1.0 - d
|
||||
};
|
||||
double k = 1.0 / (5.0 * adaptingLuminance + 1.0);
|
||||
double k4 = k * k * k * k;
|
||||
double k4F = 1.0 - k4;
|
||||
double fl = (k4 * adaptingLuminance) + (0.1 * k4F * k4F * Math.cbrt(
|
||||
5.0 * adaptingLuminance));
|
||||
double n = (ColorUtils.yFromLstar(backgroundLstar) / whitePoint[1]);
|
||||
double z = 1.48 + Math.sqrt(n);
|
||||
double nbb = 0.725 / Math.pow(n, 0.2);
|
||||
double ncb = nbb;
|
||||
double[] rgbAFactors =
|
||||
new double[]{
|
||||
Math.pow(fl * rgbD[0] * rW / 100.0, 0.42),
|
||||
Math.pow(fl * rgbD[1] * gW / 100.0, 0.42),
|
||||
Math.pow(fl * rgbD[2] * bW / 100.0, 0.42)
|
||||
};
|
||||
|
||||
double[] rgbA =
|
||||
new double[]{
|
||||
(400.0 * rgbAFactors[0]) / (rgbAFactors[0] + 27.13),
|
||||
(400.0 * rgbAFactors[1]) / (rgbAFactors[1] + 27.13),
|
||||
(400.0 * rgbAFactors[2]) / (rgbAFactors[2] + 27.13)
|
||||
};
|
||||
|
||||
double aw = ((2.0 * rgbA[0]) + rgbA[1] + (0.05 * rgbA[2])) * nbb;
|
||||
return new ViewingConditions(n, aw, nbb, ncb, c, nc, rgbD, fl, Math.pow(fl, 0.25), z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create sRGB-like viewing conditions with a custom background lstar.
|
||||
*
|
||||
* <p>Default viewing conditions have a lstar of 50, midgray.
|
||||
*/
|
||||
public static ViewingConditions defaultWithBackgroundLstar(double lstar) {
|
||||
return ViewingConditions.make(
|
||||
ColorUtils.whitePointD65(),
|
||||
(200.0 / Math.PI * ColorUtils.yFromLstar(50.0) / 100.f),
|
||||
lstar,
|
||||
2.0,
|
||||
false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters are intermediate values of the CAM16 conversion process. Their names are
|
||||
* shorthand
|
||||
* for technical color science terminology, this class would not benefit from documenting them
|
||||
* individually. A brief overview is available in the CAM16 specification, and a complete
|
||||
* overview
|
||||
* requires a color science textbook, such as Fairchild's Color Appearance Models.
|
||||
*/
|
||||
private ViewingConditions(
|
||||
double n,
|
||||
double aw,
|
||||
double nbb,
|
||||
double ncb,
|
||||
double c,
|
||||
double nc,
|
||||
double[] rgbD,
|
||||
double fl,
|
||||
double flRoot,
|
||||
double z) {
|
||||
this.n = n;
|
||||
this.aw = aw;
|
||||
this.nbb = nbb;
|
||||
this.ncb = ncb;
|
||||
this.c = c;
|
||||
this.nc = nc;
|
||||
this.rgbD = rgbD;
|
||||
this.fl = fl;
|
||||
this.flRoot = flRoot;
|
||||
this.z = z;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.palettes;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
|
||||
/**
|
||||
* An intermediate concept between the key color for a UI theme, and a full color scheme. 5 sets of
|
||||
* tones are generated, all except one use the same hue as the key color, and all vary in chroma.
|
||||
*/
|
||||
public final class CorePalette {
|
||||
public TonalPalette a1;
|
||||
public TonalPalette a2;
|
||||
public TonalPalette a3;
|
||||
public TonalPalette n1;
|
||||
public TonalPalette n2;
|
||||
public TonalPalette error;
|
||||
|
||||
/**
|
||||
* Create key tones from a color.
|
||||
*
|
||||
* @param argb ARGB representation of a color
|
||||
*/
|
||||
public static CorePalette of(int argb) {
|
||||
return new CorePalette(argb, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create content key tones from a color.
|
||||
*
|
||||
* @param argb ARGB representation of a color
|
||||
*/
|
||||
public static CorePalette contentOf(int argb) {
|
||||
return new CorePalette(argb, true);
|
||||
}
|
||||
|
||||
private CorePalette(int argb, boolean isContent) {
|
||||
Hct hct = Hct.fromInt(argb);
|
||||
double hue = hct.getHue();
|
||||
double chroma = hct.getChroma();
|
||||
if (isContent) {
|
||||
this.a1 = TonalPalette.fromHueAndChroma(hue, chroma);
|
||||
this.a2 = TonalPalette.fromHueAndChroma(hue, chroma / 3.);
|
||||
this.a3 = TonalPalette.fromHueAndChroma(hue + 60., chroma / 2.);
|
||||
this.n1 = TonalPalette.fromHueAndChroma(hue, min(chroma / 12., 4.));
|
||||
this.n2 = TonalPalette.fromHueAndChroma(hue, min(chroma / 6., 8.));
|
||||
} else {
|
||||
this.a1 = TonalPalette.fromHueAndChroma(hue, max(48., chroma));
|
||||
this.a2 = TonalPalette.fromHueAndChroma(hue, 16.);
|
||||
this.a3 = TonalPalette.fromHueAndChroma(hue + 60., 24.);
|
||||
this.n1 = TonalPalette.fromHueAndChroma(hue, 4.);
|
||||
this.n2 = TonalPalette.fromHueAndChroma(hue, 8.);
|
||||
}
|
||||
this.error = TonalPalette.fromHueAndChroma(25, 84.);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.palettes;
|
||||
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A convenience class for retrieving colors that are constant in hue and chroma, but vary in tone.
|
||||
*/
|
||||
public final class TonalPalette {
|
||||
Map<Integer, Integer> cache;
|
||||
double hue;
|
||||
double chroma;
|
||||
|
||||
/**
|
||||
* Create tones using the HCT hue and chroma from a color.
|
||||
*
|
||||
* @param argb ARGB representation of a color
|
||||
* @return Tones matching that color's hue and chroma.
|
||||
*/
|
||||
public static TonalPalette fromInt(int argb) {
|
||||
return fromHct(Hct.fromInt(argb));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create tones using a HCT color.
|
||||
*
|
||||
* @param hct HCT representation of a color.
|
||||
* @return Tones matching that color's hue and chroma.
|
||||
*/
|
||||
public static TonalPalette fromHct(Hct hct) {
|
||||
return TonalPalette.fromHueAndChroma(hct.getHue(), hct.getChroma());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create tones from a defined HCT hue and chroma.
|
||||
*
|
||||
* @param hue HCT hue
|
||||
* @param chroma HCT chroma
|
||||
* @return Tones matching hue and chroma.
|
||||
*/
|
||||
public static TonalPalette fromHueAndChroma(double hue, double chroma) {
|
||||
return new TonalPalette(hue, chroma);
|
||||
}
|
||||
|
||||
private TonalPalette(double hue, double chroma) {
|
||||
cache = new HashMap<>();
|
||||
this.hue = hue;
|
||||
this.chroma = chroma;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an ARGB color with HCT hue and chroma of this Tones instance, and the provided HCT
|
||||
* tone.
|
||||
*
|
||||
* @param tone HCT tone, measured from 0 to 100.
|
||||
* @return ARGB representation of a color with that tone.
|
||||
*/
|
||||
// AndroidJdkLibsChecker is higher priority than ComputeIfAbsentUseValue (b/119581923)
|
||||
@SuppressWarnings("ComputeIfAbsentUseValue")
|
||||
public int tone(int tone) {
|
||||
Integer color = cache.get(tone);
|
||||
if (color == null) {
|
||||
color = Hct.from(this.hue, this.chroma, tone).toInt();
|
||||
cache.put(tone, color);
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
/** Given a tone, use hue and chroma of palette to create a color, and return it as HCT. */
|
||||
public Hct getHct(double tone) {
|
||||
return Hct.from(this.hue, this.chroma, tone);
|
||||
}
|
||||
|
||||
/** The chroma of the Tonal Palette, in HCT. Ranges from 0 to ~130 (for sRGB gamut). */
|
||||
public double getChroma() {
|
||||
return this.chroma;
|
||||
}
|
||||
|
||||
/** The hue of the Tonal Palette, in HCT. Ranges from 0 to 360. */
|
||||
public double getHue() {
|
||||
return this.hue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.quantize;
|
||||
|
||||
/** An interface to allow use of different color spaces by quantizers. */
|
||||
public interface PointProvider {
|
||||
/** The three components in the color space of an sRGB color. */
|
||||
double[] fromInt(int argb);
|
||||
|
||||
/** The ARGB (i.e. hex code) representation of this color. */
|
||||
int toInt(double[] point);
|
||||
|
||||
/**
|
||||
* Squared distance between two colors.
|
||||
* Distance is defined by scientific color spaces and referred to as delta E.
|
||||
*/
|
||||
double distance(double[] a, double[] b);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.quantize;
|
||||
|
||||
import com.android.systemui.monet.utils.ColorUtils;
|
||||
|
||||
/**
|
||||
* Provides conversions needed for K-Means quantization. Converting input to points, and converting
|
||||
* the final state of the K-Means algorithm to colors.
|
||||
*/
|
||||
public final class PointProviderLab implements PointProvider {
|
||||
/**
|
||||
* Convert a color represented in ARGB to a 3-element array of L*a*b* coordinates of the color.
|
||||
*/
|
||||
@Override
|
||||
public double[] fromInt(int argb) {
|
||||
double[] lab = ColorUtils.labFromArgb(argb);
|
||||
return new double[]{lab[0], lab[1], lab[2]};
|
||||
}
|
||||
|
||||
/** Convert a 3-element array to a color represented in ARGB. */
|
||||
@Override
|
||||
public int toInt(double[] lab) {
|
||||
return ColorUtils.argbFromLab(lab[0], lab[1], lab[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard CIE 1976 delta E formula also takes the square root, unneeded here. This method is
|
||||
* used by quantization algorithms to compare distance, and the relative ordering is the same,
|
||||
* with or without a square root.
|
||||
*
|
||||
* <p>This relatively minor optimization is helpful because this method is called at least once
|
||||
* for each pixel in an image.
|
||||
*/
|
||||
@Override
|
||||
public double distance(double[] one, double[] two) {
|
||||
double dL = (one[0] - two[0]);
|
||||
double dA = (one[1] - two[1]);
|
||||
double dB = (one[2] - two[2]);
|
||||
return (dL * dL + dA * dA + dB * dB);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.quantize;
|
||||
|
||||
interface Quantizer {
|
||||
QuantizerResult quantize(int[] pixels, int maxColors);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.quantize;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An image quantizer that improves on the quality of a standard K-Means algorithm by setting the
|
||||
* K-Means initial state to the output of a Wu quantizer, instead of random centroids. Improves on
|
||||
* speed by several optimizations, as implemented in Wsmeans, or Weighted Square Means, K-Means with
|
||||
* those optimizations.
|
||||
*
|
||||
* <p>This algorithm was designed by M. Emre Celebi, and was found in their 2011 paper, Improving
|
||||
* the Performance of K-Means for Color Quantization. https://arxiv.org/abs/1101.0395
|
||||
*/
|
||||
public final class QuantizerCelebi {
|
||||
private QuantizerCelebi() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce the number of colors needed to represented the input, minimizing the difference
|
||||
* between
|
||||
* the original image and the recolored image.
|
||||
*
|
||||
* @param pixels Colors in ARGB format.
|
||||
* @param maxColors The number of colors to divide the image into. A lower number of colors may
|
||||
* be
|
||||
* returned.
|
||||
* @return Map with keys of colors in ARGB format, and values of number of pixels in the
|
||||
* original
|
||||
* image that correspond to the color in the quantized image.
|
||||
*/
|
||||
public static Map<Integer, Integer> quantize(int[] pixels, int maxColors) {
|
||||
QuantizerWu wu = new QuantizerWu();
|
||||
QuantizerResult wuResult = wu.quantize(pixels, maxColors);
|
||||
|
||||
Set<Integer> wuClustersAsObjects = wuResult.colorToCount.keySet();
|
||||
int index = 0;
|
||||
int[] wuClusters = new int[wuClustersAsObjects.size()];
|
||||
for (Integer argb : wuClustersAsObjects) {
|
||||
wuClusters[index++] = argb;
|
||||
}
|
||||
|
||||
return QuantizerWsmeans.quantize(pixels, wuClusters, maxColors);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.quantize;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/** Creates a dictionary with keys of colors, and values of count of the color */
|
||||
public final class QuantizerMap implements Quantizer {
|
||||
Map<Integer, Integer> colorToCount;
|
||||
|
||||
@Override
|
||||
public QuantizerResult quantize(int[] pixels, int colorCount) {
|
||||
final Map<Integer, Integer> pixelByCount = new LinkedHashMap<>();
|
||||
for (int pixel : pixels) {
|
||||
final Integer currentPixelCount = pixelByCount.get(pixel);
|
||||
final int newPixelCount = currentPixelCount == null ? 1 : currentPixelCount + 1;
|
||||
pixelByCount.put(pixel, newPixelCount);
|
||||
}
|
||||
colorToCount = pixelByCount;
|
||||
return new QuantizerResult(pixelByCount);
|
||||
}
|
||||
|
||||
public Map<Integer, Integer> getColorToCount() {
|
||||
return colorToCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.quantize;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/** Represents result of a quantizer run */
|
||||
public final class QuantizerResult {
|
||||
public final Map<Integer, Integer> colorToCount;
|
||||
|
||||
QuantizerResult(Map<Integer, Integer> colorToCount) {
|
||||
this.colorToCount = colorToCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.quantize;
|
||||
|
||||
import static java.lang.Math.min;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* An image quantizer that improves on the speed of a standard K-Means algorithm by implementing
|
||||
* several optimizations, including deduping identical pixels and a triangle inequality rule that
|
||||
* reduces the number of comparisons needed to identify which cluster a point should be moved to.
|
||||
*
|
||||
* <p>Wsmeans stands for Weighted Square Means.
|
||||
*
|
||||
* <p>This algorithm was designed by M. Emre Celebi, and was found in their 2011 paper, Improving
|
||||
* the Performance of K-Means for Color Quantization. https://arxiv.org/abs/1101.0395
|
||||
*/
|
||||
public final class QuantizerWsmeans {
|
||||
private QuantizerWsmeans() {
|
||||
}
|
||||
|
||||
private static final class Distance implements Comparable<Distance> {
|
||||
int index;
|
||||
double distance;
|
||||
|
||||
Distance() {
|
||||
this.index = -1;
|
||||
this.distance = -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Distance other) {
|
||||
return ((Double) this.distance).compareTo(other.distance);
|
||||
}
|
||||
}
|
||||
|
||||
private static final int MAX_ITERATIONS = 10;
|
||||
private static final double MIN_MOVEMENT_DISTANCE = 3.0;
|
||||
|
||||
/**
|
||||
* Reduce the number of colors needed to represented the input, minimizing the difference
|
||||
* between
|
||||
* the original image and the recolored image.
|
||||
*
|
||||
* @param inputPixels Colors in ARGB format.
|
||||
* @param startingClusters Defines the initial state of the quantizer. Passing an empty array
|
||||
* is
|
||||
* fine, the implementation will create its own initial state that leads
|
||||
* to reproducible
|
||||
* results for the same inputs. Passing an array that is the result of
|
||||
* Wu quantization leads
|
||||
* to higher quality results.
|
||||
* @param maxColors The number of colors to divide the image into. A lower number of
|
||||
* colors may be
|
||||
* returned.
|
||||
* @return Map with keys of colors in ARGB format, values of how many of the input pixels belong
|
||||
* to the color.
|
||||
*/
|
||||
public static Map<Integer, Integer> quantize(
|
||||
int[] inputPixels, int[] startingClusters, int maxColors) {
|
||||
// Uses a seeded random number generator to ensure consistent results.
|
||||
Random random = new Random(0x42688);
|
||||
|
||||
Map<Integer, Integer> pixelToCount = new LinkedHashMap<>();
|
||||
double[][] points = new double[inputPixels.length][];
|
||||
int[] pixels = new int[inputPixels.length];
|
||||
PointProvider pointProvider = new PointProviderLab();
|
||||
|
||||
int pointCount = 0;
|
||||
for (int i = 0; i < inputPixels.length; i++) {
|
||||
int inputPixel = inputPixels[i];
|
||||
Integer pixelCount = pixelToCount.get(inputPixel);
|
||||
if (pixelCount == null) {
|
||||
points[pointCount] = pointProvider.fromInt(inputPixel);
|
||||
pixels[pointCount] = inputPixel;
|
||||
pointCount++;
|
||||
|
||||
pixelToCount.put(inputPixel, 1);
|
||||
} else {
|
||||
pixelToCount.put(inputPixel, pixelCount + 1);
|
||||
}
|
||||
}
|
||||
|
||||
int[] counts = new int[pointCount];
|
||||
for (int i = 0; i < pointCount; i++) {
|
||||
int pixel = pixels[i];
|
||||
int count = pixelToCount.get(pixel);
|
||||
counts[i] = count;
|
||||
}
|
||||
|
||||
int clusterCount = min(maxColors, pointCount);
|
||||
if (startingClusters.length != 0) {
|
||||
clusterCount = min(clusterCount, startingClusters.length);
|
||||
}
|
||||
|
||||
double[][] clusters = new double[clusterCount][];
|
||||
for (int i = 0; i < startingClusters.length; i++) {
|
||||
clusters[i] = pointProvider.fromInt(startingClusters[i]);
|
||||
}
|
||||
|
||||
int[] clusterIndices = new int[pointCount];
|
||||
for (int i = 0; i < pointCount; i++) {
|
||||
clusterIndices[i] = random.nextInt(clusterCount);
|
||||
}
|
||||
|
||||
int[][] indexMatrix = new int[clusterCount][];
|
||||
for (int i = 0; i < clusterCount; i++) {
|
||||
indexMatrix[i] = new int[clusterCount];
|
||||
}
|
||||
|
||||
Distance[][] distanceToIndexMatrix = new Distance[clusterCount][];
|
||||
for (int i = 0; i < clusterCount; i++) {
|
||||
distanceToIndexMatrix[i] = new Distance[clusterCount];
|
||||
for (int j = 0; j < clusterCount; j++) {
|
||||
distanceToIndexMatrix[i][j] = new Distance();
|
||||
}
|
||||
}
|
||||
|
||||
int[] pixelCountSums = new int[clusterCount];
|
||||
for (int iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
|
||||
for (int i = 0; i < clusterCount; i++) {
|
||||
for (int j = i + 1; j < clusterCount; j++) {
|
||||
double distance = pointProvider.distance(clusters[i], clusters[j]);
|
||||
distanceToIndexMatrix[j][i].distance = distance;
|
||||
distanceToIndexMatrix[j][i].index = i;
|
||||
distanceToIndexMatrix[i][j].distance = distance;
|
||||
distanceToIndexMatrix[i][j].index = j;
|
||||
}
|
||||
Arrays.sort(distanceToIndexMatrix[i]);
|
||||
for (int j = 0; j < clusterCount; j++) {
|
||||
indexMatrix[i][j] = distanceToIndexMatrix[i][j].index;
|
||||
}
|
||||
}
|
||||
|
||||
int pointsMoved = 0;
|
||||
for (int i = 0; i < pointCount; i++) {
|
||||
double[] point = points[i];
|
||||
int previousClusterIndex = clusterIndices[i];
|
||||
double[] previousCluster = clusters[previousClusterIndex];
|
||||
double previousDistance = pointProvider.distance(point, previousCluster);
|
||||
|
||||
double minimumDistance = previousDistance;
|
||||
int newClusterIndex = -1;
|
||||
for (int j = 0; j < clusterCount; j++) {
|
||||
if (distanceToIndexMatrix[previousClusterIndex][j].distance
|
||||
>= 4 * previousDistance) {
|
||||
continue;
|
||||
}
|
||||
double distance = pointProvider.distance(point, clusters[j]);
|
||||
if (distance < minimumDistance) {
|
||||
minimumDistance = distance;
|
||||
newClusterIndex = j;
|
||||
}
|
||||
}
|
||||
if (newClusterIndex != -1) {
|
||||
double distanceChange =
|
||||
Math.abs(Math.sqrt(minimumDistance) - Math.sqrt(previousDistance));
|
||||
if (distanceChange > MIN_MOVEMENT_DISTANCE) {
|
||||
pointsMoved++;
|
||||
clusterIndices[i] = newClusterIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pointsMoved == 0 && iteration != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
double[] componentASums = new double[clusterCount];
|
||||
double[] componentBSums = new double[clusterCount];
|
||||
double[] componentCSums = new double[clusterCount];
|
||||
Arrays.fill(pixelCountSums, 0);
|
||||
for (int i = 0; i < pointCount; i++) {
|
||||
int clusterIndex = clusterIndices[i];
|
||||
double[] point = points[i];
|
||||
int count = counts[i];
|
||||
pixelCountSums[clusterIndex] += count;
|
||||
componentASums[clusterIndex] += (point[0] * count);
|
||||
componentBSums[clusterIndex] += (point[1] * count);
|
||||
componentCSums[clusterIndex] += (point[2] * count);
|
||||
}
|
||||
|
||||
for (int i = 0; i < clusterCount; i++) {
|
||||
int count = pixelCountSums[i];
|
||||
if (count == 0) {
|
||||
clusters[i] = new double[]{0., 0., 0.};
|
||||
continue;
|
||||
}
|
||||
double a = componentASums[i] / count;
|
||||
double b = componentBSums[i] / count;
|
||||
double c = componentCSums[i] / count;
|
||||
clusters[i][0] = a;
|
||||
clusters[i][1] = b;
|
||||
clusters[i][2] = c;
|
||||
}
|
||||
}
|
||||
|
||||
Map<Integer, Integer> argbToPopulation = new LinkedHashMap<>();
|
||||
for (int i = 0; i < clusterCount; i++) {
|
||||
int count = pixelCountSums[i];
|
||||
if (count == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int possibleNewCluster = pointProvider.toInt(clusters[i]);
|
||||
if (argbToPopulation.containsKey(possibleNewCluster)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
argbToPopulation.put(possibleNewCluster, count);
|
||||
}
|
||||
|
||||
return argbToPopulation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.quantize;
|
||||
|
||||
import com.android.systemui.monet.utils.ColorUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An image quantizer that divides the image's pixels into clusters by recursively cutting an RGB
|
||||
* cube, based on the weight of pixels in each area of the cube.
|
||||
*
|
||||
* <p>The algorithm was described by Xiaolin Wu in Graphic Gems II, published in 1991.
|
||||
*/
|
||||
public final class QuantizerWu implements Quantizer {
|
||||
int[] weights;
|
||||
int[] momentsR;
|
||||
int[] momentsG;
|
||||
int[] momentsB;
|
||||
double[] moments;
|
||||
Box[] cubes;
|
||||
|
||||
// A histogram of all the input colors is constructed. It has the shape of a
|
||||
// cube. The cube would be too large if it contained all 16 million colors:
|
||||
// historical best practice is to use 5 bits of the 8 in each channel,
|
||||
// reducing the histogram to a volume of ~32,000.
|
||||
private static final int INDEX_BITS = 5;
|
||||
private static final int INDEX_COUNT = 33; // ((1 << INDEX_BITS) + 1)
|
||||
private static final int TOTAL_SIZE = 35937; // INDEX_COUNT * INDEX_COUNT * INDEX_COUNT
|
||||
|
||||
@Override
|
||||
public QuantizerResult quantize(int[] pixels, int colorCount) {
|
||||
QuantizerResult mapResult = new QuantizerMap().quantize(pixels, colorCount);
|
||||
constructHistogram(mapResult.colorToCount);
|
||||
createMoments();
|
||||
CreateBoxesResult createBoxesResult = createBoxes(colorCount);
|
||||
List<Integer> colors = createResult(createBoxesResult.resultCount);
|
||||
Map<Integer, Integer> resultMap = new LinkedHashMap<>();
|
||||
for (int color : colors) {
|
||||
resultMap.put(color, 0);
|
||||
}
|
||||
return new QuantizerResult(resultMap);
|
||||
}
|
||||
|
||||
static int getIndex(int r, int g, int b) {
|
||||
return (r << (INDEX_BITS * 2)) + (r << (INDEX_BITS + 1)) + r + (g << INDEX_BITS) + g + b;
|
||||
}
|
||||
|
||||
void constructHistogram(Map<Integer, Integer> pixels) {
|
||||
weights = new int[TOTAL_SIZE];
|
||||
momentsR = new int[TOTAL_SIZE];
|
||||
momentsG = new int[TOTAL_SIZE];
|
||||
momentsB = new int[TOTAL_SIZE];
|
||||
moments = new double[TOTAL_SIZE];
|
||||
|
||||
for (Map.Entry<Integer, Integer> pair : pixels.entrySet()) {
|
||||
int pixel = pair.getKey();
|
||||
int count = pair.getValue();
|
||||
int red = ColorUtils.redFromArgb(pixel);
|
||||
int green = ColorUtils.greenFromArgb(pixel);
|
||||
int blue = ColorUtils.blueFromArgb(pixel);
|
||||
int bitsToRemove = 8 - INDEX_BITS;
|
||||
int iR = (red >> bitsToRemove) + 1;
|
||||
int iG = (green >> bitsToRemove) + 1;
|
||||
int iB = (blue >> bitsToRemove) + 1;
|
||||
int index = getIndex(iR, iG, iB);
|
||||
weights[index] += count;
|
||||
momentsR[index] += (red * count);
|
||||
momentsG[index] += (green * count);
|
||||
momentsB[index] += (blue * count);
|
||||
moments[index] += (count * ((red * red) + (green * green) + (blue * blue)));
|
||||
}
|
||||
}
|
||||
|
||||
void createMoments() {
|
||||
for (int r = 1; r < INDEX_COUNT; ++r) {
|
||||
int[] area = new int[INDEX_COUNT];
|
||||
int[] areaR = new int[INDEX_COUNT];
|
||||
int[] areaG = new int[INDEX_COUNT];
|
||||
int[] areaB = new int[INDEX_COUNT];
|
||||
double[] area2 = new double[INDEX_COUNT];
|
||||
|
||||
for (int g = 1; g < INDEX_COUNT; ++g) {
|
||||
int line = 0;
|
||||
int lineR = 0;
|
||||
int lineG = 0;
|
||||
int lineB = 0;
|
||||
double line2 = 0.0;
|
||||
for (int b = 1; b < INDEX_COUNT; ++b) {
|
||||
int index = getIndex(r, g, b);
|
||||
line += weights[index];
|
||||
lineR += momentsR[index];
|
||||
lineG += momentsG[index];
|
||||
lineB += momentsB[index];
|
||||
line2 += moments[index];
|
||||
|
||||
area[b] += line;
|
||||
areaR[b] += lineR;
|
||||
areaG[b] += lineG;
|
||||
areaB[b] += lineB;
|
||||
area2[b] += line2;
|
||||
|
||||
int previousIndex = getIndex(r - 1, g, b);
|
||||
weights[index] = weights[previousIndex] + area[b];
|
||||
momentsR[index] = momentsR[previousIndex] + areaR[b];
|
||||
momentsG[index] = momentsG[previousIndex] + areaG[b];
|
||||
momentsB[index] = momentsB[previousIndex] + areaB[b];
|
||||
moments[index] = moments[previousIndex] + area2[b];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CreateBoxesResult createBoxes(int maxColorCount) {
|
||||
cubes = new Box[maxColorCount];
|
||||
for (int i = 0; i < maxColorCount; i++) {
|
||||
cubes[i] = new Box();
|
||||
}
|
||||
double[] volumeVariance = new double[maxColorCount];
|
||||
Box firstBox = cubes[0];
|
||||
firstBox.r1 = INDEX_COUNT - 1;
|
||||
firstBox.g1 = INDEX_COUNT - 1;
|
||||
firstBox.b1 = INDEX_COUNT - 1;
|
||||
|
||||
int generatedColorCount = maxColorCount;
|
||||
int next = 0;
|
||||
for (int i = 1; i < maxColorCount; i++) {
|
||||
if (cut(cubes[next], cubes[i])) {
|
||||
volumeVariance[next] = (cubes[next].vol > 1) ? variance(cubes[next]) : 0.0;
|
||||
volumeVariance[i] = (cubes[i].vol > 1) ? variance(cubes[i]) : 0.0;
|
||||
} else {
|
||||
volumeVariance[next] = 0.0;
|
||||
i--;
|
||||
}
|
||||
|
||||
next = 0;
|
||||
|
||||
double temp = volumeVariance[0];
|
||||
for (int j = 1; j <= i; j++) {
|
||||
if (volumeVariance[j] > temp) {
|
||||
temp = volumeVariance[j];
|
||||
next = j;
|
||||
}
|
||||
}
|
||||
if (temp <= 0.0) {
|
||||
generatedColorCount = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new CreateBoxesResult(maxColorCount, generatedColorCount);
|
||||
}
|
||||
|
||||
List<Integer> createResult(int colorCount) {
|
||||
List<Integer> colors = new ArrayList<>();
|
||||
for (int i = 0; i < colorCount; ++i) {
|
||||
Box cube = cubes[i];
|
||||
int weight = volume(cube, weights);
|
||||
if (weight > 0) {
|
||||
int r = volume(cube, momentsR) / weight;
|
||||
int g = volume(cube, momentsG) / weight;
|
||||
int b = volume(cube, momentsB) / weight;
|
||||
int color = (255 << 24) | ((r & 0x0ff) << 16) | ((g & 0x0ff) << 8) | (b & 0x0ff);
|
||||
colors.add(color);
|
||||
}
|
||||
}
|
||||
return colors;
|
||||
}
|
||||
|
||||
double variance(Box cube) {
|
||||
int dr = volume(cube, momentsR);
|
||||
int dg = volume(cube, momentsG);
|
||||
int db = volume(cube, momentsB);
|
||||
double xx =
|
||||
moments[getIndex(cube.r1, cube.g1, cube.b1)]
|
||||
- moments[getIndex(cube.r1, cube.g1, cube.b0)]
|
||||
- moments[getIndex(cube.r1, cube.g0, cube.b1)]
|
||||
+ moments[getIndex(cube.r1, cube.g0, cube.b0)]
|
||||
- moments[getIndex(cube.r0, cube.g1, cube.b1)]
|
||||
+ moments[getIndex(cube.r0, cube.g1, cube.b0)]
|
||||
+ moments[getIndex(cube.r0, cube.g0, cube.b1)]
|
||||
- moments[getIndex(cube.r0, cube.g0, cube.b0)];
|
||||
|
||||
int hypotenuse = dr * dr + dg * dg + db * db;
|
||||
int volume = volume(cube, weights);
|
||||
return xx - hypotenuse / ((double) volume);
|
||||
}
|
||||
|
||||
Boolean cut(Box one, Box two) {
|
||||
int wholeR = volume(one, momentsR);
|
||||
int wholeG = volume(one, momentsG);
|
||||
int wholeB = volume(one, momentsB);
|
||||
int wholeW = volume(one, weights);
|
||||
|
||||
MaximizeResult maxRResult =
|
||||
maximize(one, Direction.RED, one.r0 + 1, one.r1, wholeR, wholeG, wholeB, wholeW);
|
||||
MaximizeResult maxGResult =
|
||||
maximize(one, Direction.GREEN, one.g0 + 1, one.g1, wholeR, wholeG, wholeB, wholeW);
|
||||
MaximizeResult maxBResult =
|
||||
maximize(one, Direction.BLUE, one.b0 + 1, one.b1, wholeR, wholeG, wholeB, wholeW);
|
||||
Direction cutDirection;
|
||||
double maxR = maxRResult.maximum;
|
||||
double maxG = maxGResult.maximum;
|
||||
double maxB = maxBResult.maximum;
|
||||
if (maxR >= maxG && maxR >= maxB) {
|
||||
if (maxRResult.cutLocation < 0) {
|
||||
return false;
|
||||
}
|
||||
cutDirection = Direction.RED;
|
||||
} else if (maxG >= maxR && maxG >= maxB) {
|
||||
cutDirection = Direction.GREEN;
|
||||
} else {
|
||||
cutDirection = Direction.BLUE;
|
||||
}
|
||||
|
||||
two.r1 = one.r1;
|
||||
two.g1 = one.g1;
|
||||
two.b1 = one.b1;
|
||||
|
||||
switch (cutDirection) {
|
||||
case RED:
|
||||
one.r1 = maxRResult.cutLocation;
|
||||
two.r0 = one.r1;
|
||||
two.g0 = one.g0;
|
||||
two.b0 = one.b0;
|
||||
break;
|
||||
case GREEN:
|
||||
one.g1 = maxGResult.cutLocation;
|
||||
two.r0 = one.r0;
|
||||
two.g0 = one.g1;
|
||||
two.b0 = one.b0;
|
||||
break;
|
||||
case BLUE:
|
||||
one.b1 = maxBResult.cutLocation;
|
||||
two.r0 = one.r0;
|
||||
two.g0 = one.g0;
|
||||
two.b0 = one.b1;
|
||||
break;
|
||||
}
|
||||
|
||||
one.vol = (one.r1 - one.r0) * (one.g1 - one.g0) * (one.b1 - one.b0);
|
||||
two.vol = (two.r1 - two.r0) * (two.g1 - two.g0) * (two.b1 - two.b0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
MaximizeResult maximize(
|
||||
Box cube,
|
||||
Direction direction,
|
||||
int first,
|
||||
int last,
|
||||
int wholeR,
|
||||
int wholeG,
|
||||
int wholeB,
|
||||
int wholeW) {
|
||||
int bottomR = bottom(cube, direction, momentsR);
|
||||
int bottomG = bottom(cube, direction, momentsG);
|
||||
int bottomB = bottom(cube, direction, momentsB);
|
||||
int bottomW = bottom(cube, direction, weights);
|
||||
|
||||
double max = 0.0;
|
||||
int cut = -1;
|
||||
|
||||
int halfR = 0;
|
||||
int halfG = 0;
|
||||
int halfB = 0;
|
||||
int halfW = 0;
|
||||
for (int i = first; i < last; i++) {
|
||||
halfR = bottomR + top(cube, direction, i, momentsR);
|
||||
halfG = bottomG + top(cube, direction, i, momentsG);
|
||||
halfB = bottomB + top(cube, direction, i, momentsB);
|
||||
halfW = bottomW + top(cube, direction, i, weights);
|
||||
if (halfW == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
double tempNumerator = halfR * halfR + halfG * halfG + halfB * halfB;
|
||||
double tempDenominator = halfW;
|
||||
double temp = tempNumerator / tempDenominator;
|
||||
|
||||
halfR = wholeR - halfR;
|
||||
halfG = wholeG - halfG;
|
||||
halfB = wholeB - halfB;
|
||||
halfW = wholeW - halfW;
|
||||
if (halfW == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tempNumerator = halfR * halfR + halfG * halfG + halfB * halfB;
|
||||
tempDenominator = halfW;
|
||||
temp += (tempNumerator / tempDenominator);
|
||||
|
||||
if (temp > max) {
|
||||
max = temp;
|
||||
cut = i;
|
||||
}
|
||||
}
|
||||
return new MaximizeResult(cut, max);
|
||||
}
|
||||
|
||||
static int volume(Box cube, int[] moment) {
|
||||
return (moment[getIndex(cube.r1, cube.g1, cube.b1)]
|
||||
- moment[getIndex(cube.r1, cube.g1, cube.b0)]
|
||||
- moment[getIndex(cube.r1, cube.g0, cube.b1)]
|
||||
+ moment[getIndex(cube.r1, cube.g0, cube.b0)]
|
||||
- moment[getIndex(cube.r0, cube.g1, cube.b1)]
|
||||
+ moment[getIndex(cube.r0, cube.g1, cube.b0)]
|
||||
+ moment[getIndex(cube.r0, cube.g0, cube.b1)]
|
||||
- moment[getIndex(cube.r0, cube.g0, cube.b0)]);
|
||||
}
|
||||
|
||||
static int bottom(Box cube, Direction direction, int[] moment) {
|
||||
switch (direction) {
|
||||
case RED:
|
||||
return -moment[getIndex(cube.r0, cube.g1, cube.b1)]
|
||||
+ moment[getIndex(cube.r0, cube.g1, cube.b0)]
|
||||
+ moment[getIndex(cube.r0, cube.g0, cube.b1)]
|
||||
- moment[getIndex(cube.r0, cube.g0, cube.b0)];
|
||||
case GREEN:
|
||||
return -moment[getIndex(cube.r1, cube.g0, cube.b1)]
|
||||
+ moment[getIndex(cube.r1, cube.g0, cube.b0)]
|
||||
+ moment[getIndex(cube.r0, cube.g0, cube.b1)]
|
||||
- moment[getIndex(cube.r0, cube.g0, cube.b0)];
|
||||
case BLUE:
|
||||
return -moment[getIndex(cube.r1, cube.g1, cube.b0)]
|
||||
+ moment[getIndex(cube.r1, cube.g0, cube.b0)]
|
||||
+ moment[getIndex(cube.r0, cube.g1, cube.b0)]
|
||||
- moment[getIndex(cube.r0, cube.g0, cube.b0)];
|
||||
}
|
||||
throw new IllegalArgumentException("unexpected direction " + direction);
|
||||
}
|
||||
|
||||
static int top(Box cube, Direction direction, int position, int[] moment) {
|
||||
switch (direction) {
|
||||
case RED:
|
||||
return (moment[getIndex(position, cube.g1, cube.b1)]
|
||||
- moment[getIndex(position, cube.g1, cube.b0)]
|
||||
- moment[getIndex(position, cube.g0, cube.b1)]
|
||||
+ moment[getIndex(position, cube.g0, cube.b0)]);
|
||||
case GREEN:
|
||||
return (moment[getIndex(cube.r1, position, cube.b1)]
|
||||
- moment[getIndex(cube.r1, position, cube.b0)]
|
||||
- moment[getIndex(cube.r0, position, cube.b1)]
|
||||
+ moment[getIndex(cube.r0, position, cube.b0)]);
|
||||
case BLUE:
|
||||
return (moment[getIndex(cube.r1, cube.g1, position)]
|
||||
- moment[getIndex(cube.r1, cube.g0, position)]
|
||||
- moment[getIndex(cube.r0, cube.g1, position)]
|
||||
+ moment[getIndex(cube.r0, cube.g0, position)]);
|
||||
}
|
||||
throw new IllegalArgumentException("unexpected direction " + direction);
|
||||
}
|
||||
|
||||
private enum Direction {
|
||||
RED,
|
||||
GREEN,
|
||||
BLUE
|
||||
}
|
||||
|
||||
private static final class MaximizeResult {
|
||||
// < 0 if cut impossible
|
||||
int cutLocation;
|
||||
double maximum;
|
||||
|
||||
MaximizeResult(int cut, double max) {
|
||||
this.cutLocation = cut;
|
||||
this.maximum = max;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CreateBoxesResult {
|
||||
int requestedCount;
|
||||
int resultCount;
|
||||
|
||||
CreateBoxesResult(int requestedCount, int resultCount) {
|
||||
this.requestedCount = requestedCount;
|
||||
this.resultCount = resultCount;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Box {
|
||||
int r0 = 0;
|
||||
int r1 = 0;
|
||||
int g0 = 0;
|
||||
int g1 = 0;
|
||||
int b0 = 0;
|
||||
int b1 = 0;
|
||||
int vol = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.scheme;
|
||||
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.palettes.TonalPalette;
|
||||
import com.android.systemui.monet.utils.MathUtils;
|
||||
|
||||
/**
|
||||
* Provides important settings for creating colors dynamically, and 6 color palettes. Requires: 1. A
|
||||
* color. (source color) 2. A theme. (Variant) 3. Whether or not its dark mode. 4. Contrast level.
|
||||
* (-1 to 1, currently contrast ratio 3.0 and 7.0)
|
||||
*/
|
||||
public class DynamicScheme {
|
||||
public final int sourceColorArgb;
|
||||
public final Hct sourceColorHct;
|
||||
public final Variant variant;
|
||||
public final boolean isDark;
|
||||
public final double contrastLevel;
|
||||
|
||||
public final TonalPalette primaryPalette;
|
||||
public final TonalPalette secondaryPalette;
|
||||
public final TonalPalette tertiaryPalette;
|
||||
public final TonalPalette neutralPalette;
|
||||
public final TonalPalette neutralVariantPalette;
|
||||
public final TonalPalette errorPalette;
|
||||
|
||||
public DynamicScheme(
|
||||
Hct sourceColorHct,
|
||||
Variant variant,
|
||||
boolean isDark,
|
||||
double contrastLevel,
|
||||
TonalPalette primaryPalette,
|
||||
TonalPalette secondaryPalette,
|
||||
TonalPalette tertiaryPalette,
|
||||
TonalPalette neutralPalette,
|
||||
TonalPalette neutralVariantPalette) {
|
||||
this.sourceColorArgb = sourceColorHct.toInt();
|
||||
this.sourceColorHct = sourceColorHct;
|
||||
this.variant = variant;
|
||||
this.isDark = isDark;
|
||||
this.contrastLevel = contrastLevel;
|
||||
|
||||
this.primaryPalette = primaryPalette;
|
||||
this.secondaryPalette = secondaryPalette;
|
||||
this.tertiaryPalette = tertiaryPalette;
|
||||
this.neutralPalette = neutralPalette;
|
||||
this.neutralVariantPalette = neutralVariantPalette;
|
||||
this.errorPalette = TonalPalette.fromHueAndChroma(25.0, 84.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a set of hues and set of hue rotations, locate which hues the source color's hue is
|
||||
* between, apply the rotation at the same index as the first hue in the range, and return the
|
||||
* rotated hue.
|
||||
*
|
||||
* @param sourceColorHct The color whose hue should be rotated.
|
||||
* @param hues A set of hues.
|
||||
* @param rotations A set of hue rotations.
|
||||
* @return Color's hue with a rotation applied.
|
||||
*/
|
||||
public static double getRotatedHue(Hct sourceColorHct, double[] hues, double[] rotations) {
|
||||
final double sourceHue = sourceColorHct.getHue();
|
||||
if (rotations.length == 1) {
|
||||
return MathUtils.sanitizeDegreesDouble(sourceHue + rotations[0]);
|
||||
}
|
||||
final int size = hues.length;
|
||||
for (int i = 0; i <= (size - 2); i++) {
|
||||
final double thisHue = hues[i];
|
||||
final double nextHue = hues[i + 1];
|
||||
if (thisHue < sourceHue && sourceHue < nextHue) {
|
||||
return MathUtils.sanitizeDegreesDouble(sourceHue + rotations[i]);
|
||||
}
|
||||
}
|
||||
// If this statement executes, something is wrong, there should have been a rotation
|
||||
// found using the arrays.
|
||||
return sourceHue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,784 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.scheme;
|
||||
|
||||
import com.android.systemui.monet.palettes.CorePalette;
|
||||
|
||||
/** Represents a Material color scheme, a mapping of color roles to colors. */
|
||||
public class Scheme {
|
||||
private int primary;
|
||||
private int onPrimary;
|
||||
private int primaryContainer;
|
||||
private int onPrimaryContainer;
|
||||
private int secondary;
|
||||
private int onSecondary;
|
||||
private int secondaryContainer;
|
||||
private int onSecondaryContainer;
|
||||
private int tertiary;
|
||||
private int onTertiary;
|
||||
private int tertiaryContainer;
|
||||
private int onTertiaryContainer;
|
||||
private int error;
|
||||
private int onError;
|
||||
private int errorContainer;
|
||||
private int onErrorContainer;
|
||||
private int background;
|
||||
private int onBackground;
|
||||
private int surface;
|
||||
private int onSurface;
|
||||
private int surfaceVariant;
|
||||
private int onSurfaceVariant;
|
||||
private int outline;
|
||||
private int outlineVariant;
|
||||
private int shadow;
|
||||
private int scrim;
|
||||
private int inverseSurface;
|
||||
private int inverseOnSurface;
|
||||
private int inversePrimary;
|
||||
|
||||
public Scheme() {
|
||||
}
|
||||
|
||||
public Scheme(
|
||||
int primary,
|
||||
int onPrimary,
|
||||
int primaryContainer,
|
||||
int onPrimaryContainer,
|
||||
int secondary,
|
||||
int onSecondary,
|
||||
int secondaryContainer,
|
||||
int onSecondaryContainer,
|
||||
int tertiary,
|
||||
int onTertiary,
|
||||
int tertiaryContainer,
|
||||
int onTertiaryContainer,
|
||||
int error,
|
||||
int onError,
|
||||
int errorContainer,
|
||||
int onErrorContainer,
|
||||
int background,
|
||||
int onBackground,
|
||||
int surface,
|
||||
int onSurface,
|
||||
int surfaceVariant,
|
||||
int onSurfaceVariant,
|
||||
int outline,
|
||||
int outlineVariant,
|
||||
int shadow,
|
||||
int scrim,
|
||||
int inverseSurface,
|
||||
int inverseOnSurface,
|
||||
int inversePrimary) {
|
||||
super();
|
||||
this.primary = primary;
|
||||
this.onPrimary = onPrimary;
|
||||
this.primaryContainer = primaryContainer;
|
||||
this.onPrimaryContainer = onPrimaryContainer;
|
||||
this.secondary = secondary;
|
||||
this.onSecondary = onSecondary;
|
||||
this.secondaryContainer = secondaryContainer;
|
||||
this.onSecondaryContainer = onSecondaryContainer;
|
||||
this.tertiary = tertiary;
|
||||
this.onTertiary = onTertiary;
|
||||
this.tertiaryContainer = tertiaryContainer;
|
||||
this.onTertiaryContainer = onTertiaryContainer;
|
||||
this.error = error;
|
||||
this.onError = onError;
|
||||
this.errorContainer = errorContainer;
|
||||
this.onErrorContainer = onErrorContainer;
|
||||
this.background = background;
|
||||
this.onBackground = onBackground;
|
||||
this.surface = surface;
|
||||
this.onSurface = onSurface;
|
||||
this.surfaceVariant = surfaceVariant;
|
||||
this.onSurfaceVariant = onSurfaceVariant;
|
||||
this.outline = outline;
|
||||
this.outlineVariant = outlineVariant;
|
||||
this.shadow = shadow;
|
||||
this.scrim = scrim;
|
||||
this.inverseSurface = inverseSurface;
|
||||
this.inverseOnSurface = inverseOnSurface;
|
||||
this.inversePrimary = inversePrimary;
|
||||
}
|
||||
|
||||
/** Create a light theme Scheme from a source color in ARGB, i.e. a hex code. */
|
||||
public static Scheme light(int argb) {
|
||||
return lightFromCorePalette(CorePalette.of(argb));
|
||||
}
|
||||
|
||||
/** Create a dark theme Scheme from a source color in ARGB, i.e. a hex code. */
|
||||
public static Scheme dark(int argb) {
|
||||
return darkFromCorePalette(CorePalette.of(argb));
|
||||
}
|
||||
|
||||
/** Create a light theme content-based Scheme from a source color in ARGB, i.e. a hex code. */
|
||||
public static Scheme lightContent(int argb) {
|
||||
return lightFromCorePalette(CorePalette.contentOf(argb));
|
||||
}
|
||||
|
||||
/** Create a dark theme content-based Scheme from a source color in ARGB, i.e. a hex code. */
|
||||
public static Scheme darkContent(int argb) {
|
||||
return darkFromCorePalette(CorePalette.contentOf(argb));
|
||||
}
|
||||
|
||||
private static Scheme lightFromCorePalette(CorePalette core) {
|
||||
return new Scheme()
|
||||
.withPrimary(core.a1.tone(40))
|
||||
.withOnPrimary(core.a1.tone(100))
|
||||
.withPrimaryContainer(core.a1.tone(90))
|
||||
.withOnPrimaryContainer(core.a1.tone(10))
|
||||
.withSecondary(core.a2.tone(40))
|
||||
.withOnSecondary(core.a2.tone(100))
|
||||
.withSecondaryContainer(core.a2.tone(90))
|
||||
.withOnSecondaryContainer(core.a2.tone(10))
|
||||
.withTertiary(core.a3.tone(40))
|
||||
.withOnTertiary(core.a3.tone(100))
|
||||
.withTertiaryContainer(core.a3.tone(90))
|
||||
.withOnTertiaryContainer(core.a3.tone(10))
|
||||
.withError(core.error.tone(40))
|
||||
.withOnError(core.error.tone(100))
|
||||
.withErrorContainer(core.error.tone(90))
|
||||
.withOnErrorContainer(core.error.tone(10))
|
||||
.withBackground(core.n1.tone(99))
|
||||
.withOnBackground(core.n1.tone(10))
|
||||
.withSurface(core.n1.tone(99))
|
||||
.withOnSurface(core.n1.tone(10))
|
||||
.withSurfaceVariant(core.n2.tone(90))
|
||||
.withOnSurfaceVariant(core.n2.tone(30))
|
||||
.withOutline(core.n2.tone(50))
|
||||
.withOutlineVariant(core.n2.tone(80))
|
||||
.withShadow(core.n1.tone(0))
|
||||
.withScrim(core.n1.tone(0))
|
||||
.withInverseSurface(core.n1.tone(20))
|
||||
.withInverseOnSurface(core.n1.tone(95))
|
||||
.withInversePrimary(core.a1.tone(80));
|
||||
}
|
||||
|
||||
private static Scheme darkFromCorePalette(CorePalette core) {
|
||||
return new Scheme()
|
||||
.withPrimary(core.a1.tone(80))
|
||||
.withOnPrimary(core.a1.tone(20))
|
||||
.withPrimaryContainer(core.a1.tone(30))
|
||||
.withOnPrimaryContainer(core.a1.tone(90))
|
||||
.withSecondary(core.a2.tone(80))
|
||||
.withOnSecondary(core.a2.tone(20))
|
||||
.withSecondaryContainer(core.a2.tone(30))
|
||||
.withOnSecondaryContainer(core.a2.tone(90))
|
||||
.withTertiary(core.a3.tone(80))
|
||||
.withOnTertiary(core.a3.tone(20))
|
||||
.withTertiaryContainer(core.a3.tone(30))
|
||||
.withOnTertiaryContainer(core.a3.tone(90))
|
||||
.withError(core.error.tone(80))
|
||||
.withOnError(core.error.tone(20))
|
||||
.withErrorContainer(core.error.tone(30))
|
||||
.withOnErrorContainer(core.error.tone(80))
|
||||
.withBackground(core.n1.tone(10))
|
||||
.withOnBackground(core.n1.tone(90))
|
||||
.withSurface(core.n1.tone(10))
|
||||
.withOnSurface(core.n1.tone(90))
|
||||
.withSurfaceVariant(core.n2.tone(30))
|
||||
.withOnSurfaceVariant(core.n2.tone(80))
|
||||
.withOutline(core.n2.tone(60))
|
||||
.withOutlineVariant(core.n2.tone(30))
|
||||
.withShadow(core.n1.tone(0))
|
||||
.withScrim(core.n1.tone(0))
|
||||
.withInverseSurface(core.n1.tone(90))
|
||||
.withInverseOnSurface(core.n1.tone(20))
|
||||
.withInversePrimary(core.a1.tone(40));
|
||||
}
|
||||
|
||||
public int getPrimary() {
|
||||
return primary;
|
||||
}
|
||||
|
||||
public void setPrimary(int primary) {
|
||||
this.primary = primary;
|
||||
}
|
||||
|
||||
public Scheme withPrimary(int primary) {
|
||||
this.primary = primary;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getOnPrimary() {
|
||||
return onPrimary;
|
||||
}
|
||||
|
||||
public void setOnPrimary(int onPrimary) {
|
||||
this.onPrimary = onPrimary;
|
||||
}
|
||||
|
||||
public Scheme withOnPrimary(int onPrimary) {
|
||||
this.onPrimary = onPrimary;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getPrimaryContainer() {
|
||||
return primaryContainer;
|
||||
}
|
||||
|
||||
public void setPrimaryContainer(int primaryContainer) {
|
||||
this.primaryContainer = primaryContainer;
|
||||
}
|
||||
|
||||
public Scheme withPrimaryContainer(int primaryContainer) {
|
||||
this.primaryContainer = primaryContainer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getOnPrimaryContainer() {
|
||||
return onPrimaryContainer;
|
||||
}
|
||||
|
||||
public void setOnPrimaryContainer(int onPrimaryContainer) {
|
||||
this.onPrimaryContainer = onPrimaryContainer;
|
||||
}
|
||||
|
||||
public Scheme withOnPrimaryContainer(int onPrimaryContainer) {
|
||||
this.onPrimaryContainer = onPrimaryContainer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getSecondary() {
|
||||
return secondary;
|
||||
}
|
||||
|
||||
public void setSecondary(int secondary) {
|
||||
this.secondary = secondary;
|
||||
}
|
||||
|
||||
public Scheme withSecondary(int secondary) {
|
||||
this.secondary = secondary;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getOnSecondary() {
|
||||
return onSecondary;
|
||||
}
|
||||
|
||||
public void setOnSecondary(int onSecondary) {
|
||||
this.onSecondary = onSecondary;
|
||||
}
|
||||
|
||||
public Scheme withOnSecondary(int onSecondary) {
|
||||
this.onSecondary = onSecondary;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getSecondaryContainer() {
|
||||
return secondaryContainer;
|
||||
}
|
||||
|
||||
public void setSecondaryContainer(int secondaryContainer) {
|
||||
this.secondaryContainer = secondaryContainer;
|
||||
}
|
||||
|
||||
public Scheme withSecondaryContainer(int secondaryContainer) {
|
||||
this.secondaryContainer = secondaryContainer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getOnSecondaryContainer() {
|
||||
return onSecondaryContainer;
|
||||
}
|
||||
|
||||
public void setOnSecondaryContainer(int onSecondaryContainer) {
|
||||
this.onSecondaryContainer = onSecondaryContainer;
|
||||
}
|
||||
|
||||
public Scheme withOnSecondaryContainer(int onSecondaryContainer) {
|
||||
this.onSecondaryContainer = onSecondaryContainer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getTertiary() {
|
||||
return tertiary;
|
||||
}
|
||||
|
||||
public void setTertiary(int tertiary) {
|
||||
this.tertiary = tertiary;
|
||||
}
|
||||
|
||||
public Scheme withTertiary(int tertiary) {
|
||||
this.tertiary = tertiary;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getOnTertiary() {
|
||||
return onTertiary;
|
||||
}
|
||||
|
||||
public void setOnTertiary(int onTertiary) {
|
||||
this.onTertiary = onTertiary;
|
||||
}
|
||||
|
||||
public Scheme withOnTertiary(int onTertiary) {
|
||||
this.onTertiary = onTertiary;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getTertiaryContainer() {
|
||||
return tertiaryContainer;
|
||||
}
|
||||
|
||||
public void setTertiaryContainer(int tertiaryContainer) {
|
||||
this.tertiaryContainer = tertiaryContainer;
|
||||
}
|
||||
|
||||
public Scheme withTertiaryContainer(int tertiaryContainer) {
|
||||
this.tertiaryContainer = tertiaryContainer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getOnTertiaryContainer() {
|
||||
return onTertiaryContainer;
|
||||
}
|
||||
|
||||
public void setOnTertiaryContainer(int onTertiaryContainer) {
|
||||
this.onTertiaryContainer = onTertiaryContainer;
|
||||
}
|
||||
|
||||
public Scheme withOnTertiaryContainer(int onTertiaryContainer) {
|
||||
this.onTertiaryContainer = onTertiaryContainer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public void setError(int error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public Scheme withError(int error) {
|
||||
this.error = error;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getOnError() {
|
||||
return onError;
|
||||
}
|
||||
|
||||
public void setOnError(int onError) {
|
||||
this.onError = onError;
|
||||
}
|
||||
|
||||
public Scheme withOnError(int onError) {
|
||||
this.onError = onError;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getErrorContainer() {
|
||||
return errorContainer;
|
||||
}
|
||||
|
||||
public void setErrorContainer(int errorContainer) {
|
||||
this.errorContainer = errorContainer;
|
||||
}
|
||||
|
||||
public Scheme withErrorContainer(int errorContainer) {
|
||||
this.errorContainer = errorContainer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getOnErrorContainer() {
|
||||
return onErrorContainer;
|
||||
}
|
||||
|
||||
public void setOnErrorContainer(int onErrorContainer) {
|
||||
this.onErrorContainer = onErrorContainer;
|
||||
}
|
||||
|
||||
public Scheme withOnErrorContainer(int onErrorContainer) {
|
||||
this.onErrorContainer = onErrorContainer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getBackground() {
|
||||
return background;
|
||||
}
|
||||
|
||||
public void setBackground(int background) {
|
||||
this.background = background;
|
||||
}
|
||||
|
||||
public Scheme withBackground(int background) {
|
||||
this.background = background;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getOnBackground() {
|
||||
return onBackground;
|
||||
}
|
||||
|
||||
public void setOnBackground(int onBackground) {
|
||||
this.onBackground = onBackground;
|
||||
}
|
||||
|
||||
public Scheme withOnBackground(int onBackground) {
|
||||
this.onBackground = onBackground;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getSurface() {
|
||||
return surface;
|
||||
}
|
||||
|
||||
public void setSurface(int surface) {
|
||||
this.surface = surface;
|
||||
}
|
||||
|
||||
public Scheme withSurface(int surface) {
|
||||
this.surface = surface;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getOnSurface() {
|
||||
return onSurface;
|
||||
}
|
||||
|
||||
public void setOnSurface(int onSurface) {
|
||||
this.onSurface = onSurface;
|
||||
}
|
||||
|
||||
public Scheme withOnSurface(int onSurface) {
|
||||
this.onSurface = onSurface;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getSurfaceVariant() {
|
||||
return surfaceVariant;
|
||||
}
|
||||
|
||||
public void setSurfaceVariant(int surfaceVariant) {
|
||||
this.surfaceVariant = surfaceVariant;
|
||||
}
|
||||
|
||||
public Scheme withSurfaceVariant(int surfaceVariant) {
|
||||
this.surfaceVariant = surfaceVariant;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getOnSurfaceVariant() {
|
||||
return onSurfaceVariant;
|
||||
}
|
||||
|
||||
public void setOnSurfaceVariant(int onSurfaceVariant) {
|
||||
this.onSurfaceVariant = onSurfaceVariant;
|
||||
}
|
||||
|
||||
public Scheme withOnSurfaceVariant(int onSurfaceVariant) {
|
||||
this.onSurfaceVariant = onSurfaceVariant;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getOutline() {
|
||||
return outline;
|
||||
}
|
||||
|
||||
public void setOutline(int outline) {
|
||||
this.outline = outline;
|
||||
}
|
||||
|
||||
public Scheme withOutline(int outline) {
|
||||
this.outline = outline;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getOutlineVariant() {
|
||||
return outlineVariant;
|
||||
}
|
||||
|
||||
public void setOutlineVariant(int outlineVariant) {
|
||||
this.outlineVariant = outlineVariant;
|
||||
}
|
||||
|
||||
public Scheme withOutlineVariant(int outlineVariant) {
|
||||
this.outlineVariant = outlineVariant;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getShadow() {
|
||||
return shadow;
|
||||
}
|
||||
|
||||
public void setShadow(int shadow) {
|
||||
this.shadow = shadow;
|
||||
}
|
||||
|
||||
public Scheme withShadow(int shadow) {
|
||||
this.shadow = shadow;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getScrim() {
|
||||
return scrim;
|
||||
}
|
||||
|
||||
public void setScrim(int scrim) {
|
||||
this.scrim = scrim;
|
||||
}
|
||||
|
||||
public Scheme withScrim(int scrim) {
|
||||
this.scrim = scrim;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getInverseSurface() {
|
||||
return inverseSurface;
|
||||
}
|
||||
|
||||
public void setInverseSurface(int inverseSurface) {
|
||||
this.inverseSurface = inverseSurface;
|
||||
}
|
||||
|
||||
public Scheme withInverseSurface(int inverseSurface) {
|
||||
this.inverseSurface = inverseSurface;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getInverseOnSurface() {
|
||||
return inverseOnSurface;
|
||||
}
|
||||
|
||||
public void setInverseOnSurface(int inverseOnSurface) {
|
||||
this.inverseOnSurface = inverseOnSurface;
|
||||
}
|
||||
|
||||
public Scheme withInverseOnSurface(int inverseOnSurface) {
|
||||
this.inverseOnSurface = inverseOnSurface;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getInversePrimary() {
|
||||
return inversePrimary;
|
||||
}
|
||||
|
||||
public void setInversePrimary(int inversePrimary) {
|
||||
this.inversePrimary = inversePrimary;
|
||||
}
|
||||
|
||||
public Scheme withInversePrimary(int inversePrimary) {
|
||||
this.inversePrimary = inversePrimary;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Scheme{"
|
||||
+ "primary="
|
||||
+ primary
|
||||
+ ", onPrimary="
|
||||
+ onPrimary
|
||||
+ ", primaryContainer="
|
||||
+ primaryContainer
|
||||
+ ", onPrimaryContainer="
|
||||
+ onPrimaryContainer
|
||||
+ ", secondary="
|
||||
+ secondary
|
||||
+ ", onSecondary="
|
||||
+ onSecondary
|
||||
+ ", secondaryContainer="
|
||||
+ secondaryContainer
|
||||
+ ", onSecondaryContainer="
|
||||
+ onSecondaryContainer
|
||||
+ ", tertiary="
|
||||
+ tertiary
|
||||
+ ", onTertiary="
|
||||
+ onTertiary
|
||||
+ ", tertiaryContainer="
|
||||
+ tertiaryContainer
|
||||
+ ", onTertiaryContainer="
|
||||
+ onTertiaryContainer
|
||||
+ ", error="
|
||||
+ error
|
||||
+ ", onError="
|
||||
+ onError
|
||||
+ ", errorContainer="
|
||||
+ errorContainer
|
||||
+ ", onErrorContainer="
|
||||
+ onErrorContainer
|
||||
+ ", background="
|
||||
+ background
|
||||
+ ", onBackground="
|
||||
+ onBackground
|
||||
+ ", surface="
|
||||
+ surface
|
||||
+ ", onSurface="
|
||||
+ onSurface
|
||||
+ ", surfaceVariant="
|
||||
+ surfaceVariant
|
||||
+ ", onSurfaceVariant="
|
||||
+ onSurfaceVariant
|
||||
+ ", outline="
|
||||
+ outline
|
||||
+ ", outlineVariant="
|
||||
+ outlineVariant
|
||||
+ ", shadow="
|
||||
+ shadow
|
||||
+ ", scrim="
|
||||
+ scrim
|
||||
+ ", inverseSurface="
|
||||
+ inverseSurface
|
||||
+ ", inverseOnSurface="
|
||||
+ inverseOnSurface
|
||||
+ ", inversePrimary="
|
||||
+ inversePrimary
|
||||
+ '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
}
|
||||
if (!(object instanceof Scheme)) {
|
||||
return false;
|
||||
}
|
||||
if (!super.equals(object)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Scheme scheme = (Scheme) object;
|
||||
|
||||
if (primary != scheme.primary) {
|
||||
return false;
|
||||
}
|
||||
if (onPrimary != scheme.onPrimary) {
|
||||
return false;
|
||||
}
|
||||
if (primaryContainer != scheme.primaryContainer) {
|
||||
return false;
|
||||
}
|
||||
if (onPrimaryContainer != scheme.onPrimaryContainer) {
|
||||
return false;
|
||||
}
|
||||
if (secondary != scheme.secondary) {
|
||||
return false;
|
||||
}
|
||||
if (onSecondary != scheme.onSecondary) {
|
||||
return false;
|
||||
}
|
||||
if (secondaryContainer != scheme.secondaryContainer) {
|
||||
return false;
|
||||
}
|
||||
if (onSecondaryContainer != scheme.onSecondaryContainer) {
|
||||
return false;
|
||||
}
|
||||
if (tertiary != scheme.tertiary) {
|
||||
return false;
|
||||
}
|
||||
if (onTertiary != scheme.onTertiary) {
|
||||
return false;
|
||||
}
|
||||
if (tertiaryContainer != scheme.tertiaryContainer) {
|
||||
return false;
|
||||
}
|
||||
if (onTertiaryContainer != scheme.onTertiaryContainer) {
|
||||
return false;
|
||||
}
|
||||
if (error != scheme.error) {
|
||||
return false;
|
||||
}
|
||||
if (onError != scheme.onError) {
|
||||
return false;
|
||||
}
|
||||
if (errorContainer != scheme.errorContainer) {
|
||||
return false;
|
||||
}
|
||||
if (onErrorContainer != scheme.onErrorContainer) {
|
||||
return false;
|
||||
}
|
||||
if (background != scheme.background) {
|
||||
return false;
|
||||
}
|
||||
if (onBackground != scheme.onBackground) {
|
||||
return false;
|
||||
}
|
||||
if (surface != scheme.surface) {
|
||||
return false;
|
||||
}
|
||||
if (onSurface != scheme.onSurface) {
|
||||
return false;
|
||||
}
|
||||
if (surfaceVariant != scheme.surfaceVariant) {
|
||||
return false;
|
||||
}
|
||||
if (onSurfaceVariant != scheme.onSurfaceVariant) {
|
||||
return false;
|
||||
}
|
||||
if (outline != scheme.outline) {
|
||||
return false;
|
||||
}
|
||||
if (outlineVariant != scheme.outlineVariant) {
|
||||
return false;
|
||||
}
|
||||
if (shadow != scheme.shadow) {
|
||||
return false;
|
||||
}
|
||||
if (scrim != scheme.scrim) {
|
||||
return false;
|
||||
}
|
||||
if (inverseSurface != scheme.inverseSurface) {
|
||||
return false;
|
||||
}
|
||||
if (inverseOnSurface != scheme.inverseOnSurface) {
|
||||
return false;
|
||||
}
|
||||
if (inversePrimary != scheme.inversePrimary) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
result = 31 * result + primary;
|
||||
result = 31 * result + onPrimary;
|
||||
result = 31 * result + primaryContainer;
|
||||
result = 31 * result + onPrimaryContainer;
|
||||
result = 31 * result + secondary;
|
||||
result = 31 * result + onSecondary;
|
||||
result = 31 * result + secondaryContainer;
|
||||
result = 31 * result + onSecondaryContainer;
|
||||
result = 31 * result + tertiary;
|
||||
result = 31 * result + onTertiary;
|
||||
result = 31 * result + tertiaryContainer;
|
||||
result = 31 * result + onTertiaryContainer;
|
||||
result = 31 * result + error;
|
||||
result = 31 * result + onError;
|
||||
result = 31 * result + errorContainer;
|
||||
result = 31 * result + onErrorContainer;
|
||||
result = 31 * result + background;
|
||||
result = 31 * result + onBackground;
|
||||
result = 31 * result + surface;
|
||||
result = 31 * result + onSurface;
|
||||
result = 31 * result + surfaceVariant;
|
||||
result = 31 * result + onSurfaceVariant;
|
||||
result = 31 * result + outline;
|
||||
result = 31 * result + outlineVariant;
|
||||
result = 31 * result + shadow;
|
||||
result = 31 * result + scrim;
|
||||
result = 31 * result + inverseSurface;
|
||||
result = 31 * result + inverseOnSurface;
|
||||
result = 31 * result + inversePrimary;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.scheme;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
|
||||
import com.android.systemui.monet.dislike.DislikeAnalyzer;
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.palettes.TonalPalette;
|
||||
import com.android.systemui.monet.temperature.TemperatureCache;
|
||||
|
||||
/**
|
||||
* A scheme that places the source color in Scheme.primaryContainer.
|
||||
*
|
||||
* <p>Primary Container is the source color, adjusted for color relativity. It maintains constant
|
||||
* appearance in light mode and dark mode. This adds ~5 tone in light mode, and subtracts ~5 tone in
|
||||
* dark mode.
|
||||
*
|
||||
* <p>Tertiary Container is an analogous color, specifically, the analog of a color wheel divided
|
||||
* into 6, and the precise analog is the one found by increasing hue. This is a scientifically
|
||||
* grounded equivalent to rotating hue clockwise by 60 degrees. It also maintains constant
|
||||
* appearance.
|
||||
*/
|
||||
public class SchemeContent extends DynamicScheme {
|
||||
public SchemeContent(Hct sourceColorHct, boolean isDark, double contrastLevel) {
|
||||
super(
|
||||
sourceColorHct,
|
||||
Variant.CONTENT,
|
||||
isDark,
|
||||
contrastLevel,
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), sourceColorHct.getChroma()),
|
||||
TonalPalette.fromHueAndChroma(
|
||||
sourceColorHct.getHue(),
|
||||
max(sourceColorHct.getChroma() - 32.0, sourceColorHct.getChroma() * 0.5)),
|
||||
TonalPalette.fromHct(
|
||||
DislikeAnalyzer.fixIfDisliked(
|
||||
new TemperatureCache(sourceColorHct)
|
||||
.getAnalogousColors(/* count= */ 3, /* divisions= */ 6)
|
||||
.get(2))),
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(),
|
||||
sourceColorHct.getChroma() / 8.0),
|
||||
TonalPalette.fromHueAndChroma(
|
||||
sourceColorHct.getHue(), (sourceColorHct.getChroma() / 8.0) + 4.0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.scheme;
|
||||
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.palettes.TonalPalette;
|
||||
import com.android.systemui.monet.utils.MathUtils;
|
||||
|
||||
/** A playful theme - the source color's hue does not appear in the theme. */
|
||||
public class SchemeExpressive extends DynamicScheme {
|
||||
// NOMUTANTS--arbitrary increments/decrements, correctly, still passes tests.
|
||||
private static final double[] HUES = {0, 21, 51, 121, 151, 191, 271, 321, 360};
|
||||
private static final double[] SECONDARY_ROTATIONS = {45, 95, 45, 20, 45, 90, 45, 45, 45};
|
||||
private static final double[] TERTIARY_ROTATIONS = {120, 120, 20, 45, 20, 15, 20, 120, 120};
|
||||
|
||||
public SchemeExpressive(Hct sourceColorHct, boolean isDark, double contrastLevel) {
|
||||
super(
|
||||
sourceColorHct,
|
||||
Variant.EXPRESSIVE,
|
||||
isDark,
|
||||
contrastLevel,
|
||||
TonalPalette.fromHueAndChroma(
|
||||
MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 120.0), 40.0),
|
||||
TonalPalette.fromHueAndChroma(
|
||||
DynamicScheme.getRotatedHue(sourceColorHct, HUES, SECONDARY_ROTATIONS),
|
||||
24.0),
|
||||
TonalPalette.fromHueAndChroma(
|
||||
DynamicScheme.getRotatedHue(sourceColorHct, HUES, TERTIARY_ROTATIONS),
|
||||
32.0),
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 8.0),
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 12.0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.scheme;
|
||||
|
||||
import com.android.systemui.monet.dislike.DislikeAnalyzer;
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.palettes.TonalPalette;
|
||||
import com.android.systemui.monet.temperature.TemperatureCache;
|
||||
|
||||
/**
|
||||
* A scheme that places the source color in Scheme.primaryContainer.
|
||||
*
|
||||
* <p>Primary Container is the source color, adjusted for color relativity. It maintains constant
|
||||
* appearance in light mode and dark mode. This adds ~5 tone in light mode, and subtracts ~5 tone in
|
||||
* dark mode.
|
||||
*
|
||||
* <p>Tertiary Container is the complement to the source color, using TemperatureCache. It also
|
||||
* maintains constant appearance.
|
||||
*/
|
||||
public class SchemeFidelity extends DynamicScheme {
|
||||
public SchemeFidelity(Hct sourceColorHct, boolean isDark, double contrastLevel) {
|
||||
super(
|
||||
sourceColorHct,
|
||||
Variant.FIDELITY,
|
||||
isDark,
|
||||
contrastLevel,
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), sourceColorHct.getChroma()),
|
||||
TonalPalette.fromHueAndChroma(
|
||||
sourceColorHct.getHue(),
|
||||
Math.max(sourceColorHct.getChroma() - 32.0,
|
||||
sourceColorHct.getChroma() * 0.5)),
|
||||
TonalPalette.fromHct(
|
||||
DislikeAnalyzer.fixIfDisliked(
|
||||
new TemperatureCache(sourceColorHct).getComplement())),
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(),
|
||||
sourceColorHct.getChroma() / 8.0),
|
||||
TonalPalette.fromHueAndChroma(
|
||||
sourceColorHct.getHue(), (sourceColorHct.getChroma() / 8.0) + 4.0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.scheme;
|
||||
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.palettes.TonalPalette;
|
||||
import com.android.systemui.monet.utils.MathUtils;
|
||||
|
||||
/** A playful theme - the source color's hue does not appear in the theme. */
|
||||
public class SchemeFruitSalad extends DynamicScheme {
|
||||
public SchemeFruitSalad(Hct sourceColorHct, boolean isDark, double contrastLevel) {
|
||||
super(
|
||||
sourceColorHct,
|
||||
Variant.FRUIT_SALAD,
|
||||
isDark,
|
||||
contrastLevel,
|
||||
TonalPalette.fromHueAndChroma(
|
||||
MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() - 50.0), 48.0),
|
||||
TonalPalette.fromHueAndChroma(
|
||||
MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() - 50.0), 36.0),
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 36.0),
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 10.0),
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 16.0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.scheme;
|
||||
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.palettes.TonalPalette;
|
||||
|
||||
/** A monochrome theme, colors are purely black / white / gray. */
|
||||
public class SchemeMonochrome extends DynamicScheme {
|
||||
public SchemeMonochrome(Hct sourceColorHct, boolean isDark, double contrastLevel) {
|
||||
super(
|
||||
sourceColorHct,
|
||||
Variant.MONOCHROME,
|
||||
isDark,
|
||||
contrastLevel,
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0),
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0),
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0),
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0),
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.scheme;
|
||||
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.palettes.TonalPalette;
|
||||
|
||||
/** A theme that's slightly more chromatic than monochrome, which is purely black / white / gray. */
|
||||
public class SchemeNeutral extends DynamicScheme {
|
||||
public SchemeNeutral(Hct sourceColorHct, boolean isDark, double contrastLevel) {
|
||||
super(
|
||||
sourceColorHct,
|
||||
Variant.NEUTRAL,
|
||||
isDark,
|
||||
contrastLevel,
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 12.0),
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 8.0),
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 16.0),
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 2.0),
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 2.0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.scheme;
|
||||
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.palettes.TonalPalette;
|
||||
import com.android.systemui.monet.utils.MathUtils;
|
||||
|
||||
/** A playful theme - the source color's hue does not appear in the theme. */
|
||||
public class SchemeRainbow extends DynamicScheme {
|
||||
public SchemeRainbow(Hct sourceColorHct, boolean isDark, double contrastLevel) {
|
||||
super(
|
||||
sourceColorHct,
|
||||
Variant.RAINBOW,
|
||||
isDark,
|
||||
contrastLevel,
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 48.0),
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 16.0),
|
||||
TonalPalette.fromHueAndChroma(
|
||||
MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 60.0), 24.0),
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0),
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.scheme;
|
||||
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.palettes.TonalPalette;
|
||||
import com.android.systemui.monet.utils.MathUtils;
|
||||
|
||||
/** A calm theme, sedated colors that aren't particularly chromatic. */
|
||||
public class SchemeTonalSpot extends DynamicScheme {
|
||||
public SchemeTonalSpot(Hct sourceColorHct, boolean isDark, double contrastLevel) {
|
||||
super(
|
||||
sourceColorHct,
|
||||
Variant.TONAL_SPOT,
|
||||
isDark,
|
||||
contrastLevel,
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 40.0),
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 16.0),
|
||||
TonalPalette.fromHueAndChroma(
|
||||
MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 60.0), 24.0),
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 6.0),
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 8.0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.scheme;
|
||||
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.palettes.TonalPalette;
|
||||
|
||||
/** A loud theme, colorfulness is maximum for Primary palette, increased for others. */
|
||||
public class SchemeVibrant extends DynamicScheme {
|
||||
private static final double[] HUES = {0, 41, 61, 101, 131, 181, 251, 301, 360};
|
||||
private static final double[] SECONDARY_ROTATIONS = {18, 15, 10, 12, 15, 18, 15, 12, 12};
|
||||
private static final double[] TERTIARY_ROTATIONS = {35, 30, 20, 25, 30, 35, 30, 25, 25};
|
||||
|
||||
public SchemeVibrant(Hct sourceColorHct, boolean isDark, double contrastLevel) {
|
||||
super(
|
||||
sourceColorHct,
|
||||
Variant.VIBRANT,
|
||||
isDark,
|
||||
contrastLevel,
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 200.0),
|
||||
TonalPalette.fromHueAndChroma(
|
||||
DynamicScheme.getRotatedHue(sourceColorHct, HUES, SECONDARY_ROTATIONS),
|
||||
24.0),
|
||||
TonalPalette.fromHueAndChroma(
|
||||
DynamicScheme.getRotatedHue(sourceColorHct, HUES, TERTIARY_ROTATIONS),
|
||||
32.0),
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 8.0),
|
||||
TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 12.0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.scheme;
|
||||
|
||||
/** Themes for Dynamic Color. */
|
||||
public enum Variant {
|
||||
MONOCHROME,
|
||||
NEUTRAL,
|
||||
TONAL_SPOT,
|
||||
VIBRANT,
|
||||
EXPRESSIVE,
|
||||
FIDELITY,
|
||||
CONTENT,
|
||||
RAINBOW,
|
||||
FRUIT_SALAD
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.score;
|
||||
|
||||
import com.android.systemui.monet.hct.Cam16;
|
||||
import com.android.systemui.monet.utils.ColorUtils;
|
||||
import com.android.systemui.monet.utils.MathUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Given a large set of colors, remove colors that are unsuitable for a UI theme, and rank the rest
|
||||
* based on suitability.
|
||||
*
|
||||
* <p>Enables use of a high cluster count for image quantization, thus ensuring colors aren't
|
||||
* muddied, while curating the high cluster count to a much smaller number of appropriate choices.
|
||||
*/
|
||||
public final class Score {
|
||||
private static final double CUTOFF_CHROMA = 15.;
|
||||
private static final double CUTOFF_EXCITED_PROPORTION = 0.01;
|
||||
private static final double CUTOFF_TONE = 10.;
|
||||
private static final double TARGET_CHROMA = 48.;
|
||||
private static final double WEIGHT_PROPORTION = 0.7;
|
||||
private static final double WEIGHT_CHROMA_ABOVE = 0.3;
|
||||
private static final double WEIGHT_CHROMA_BELOW = 0.1;
|
||||
|
||||
private Score() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a map with keys of colors and values of how often the color appears, rank the colors
|
||||
* based on suitability for being used for a UI theme.
|
||||
*
|
||||
* @param colorsToPopulation map with keys of colors and values of how often the color appears,
|
||||
* usually from a source image.
|
||||
* @return Colors sorted by suitability for a UI theme. The most suitable color is the first
|
||||
* item,
|
||||
* the least suitable is the last. There will always be at least one color returned. If all
|
||||
* the input colors were not suitable for a theme, a default fallback color will be provided.
|
||||
*/
|
||||
public static List<Integer> score(Map<Integer, Integer> colorsToPopulation) {
|
||||
// Determine the total count of all colors.
|
||||
double populationSum = 0.;
|
||||
for (Map.Entry<Integer, Integer> entry : colorsToPopulation.entrySet()) {
|
||||
populationSum += entry.getValue();
|
||||
}
|
||||
|
||||
// Turn the count of each color into a proportion by dividing by the total
|
||||
// count. Also, fill a cache of CAM16 colors representing each color, and
|
||||
// record the proportion of colors for each CAM16 hue.
|
||||
Map<Integer, Cam16> colorsToCam = new HashMap<>();
|
||||
double[] hueProportions = new double[361];
|
||||
for (Map.Entry<Integer, Integer> entry : colorsToPopulation.entrySet()) {
|
||||
int color = entry.getKey();
|
||||
double population = entry.getValue();
|
||||
double proportion = population / populationSum;
|
||||
|
||||
Cam16 cam = Cam16.fromInt(color);
|
||||
colorsToCam.put(color, cam);
|
||||
|
||||
int hue = (int) Math.round(cam.getHue());
|
||||
hueProportions[hue] += proportion;
|
||||
}
|
||||
|
||||
// Determine the proportion of the colors around each color, by summing the
|
||||
// proportions around each color's hue.
|
||||
Map<Integer, Double> colorsToExcitedProportion = new HashMap<>();
|
||||
for (Map.Entry<Integer, Cam16> entry : colorsToCam.entrySet()) {
|
||||
int color = entry.getKey();
|
||||
Cam16 cam = entry.getValue();
|
||||
int hue = (int) Math.round(cam.getHue());
|
||||
|
||||
double excitedProportion = 0.;
|
||||
for (int j = (hue - 15); j < (hue + 15); j++) {
|
||||
int neighborHue = MathUtils.sanitizeDegreesInt(j);
|
||||
excitedProportion += hueProportions[neighborHue];
|
||||
}
|
||||
|
||||
colorsToExcitedProportion.put(color, excitedProportion);
|
||||
}
|
||||
|
||||
// Score the colors by their proportion, as well as how chromatic they are.
|
||||
Map<Integer, Double> colorsToScore = new HashMap<>();
|
||||
for (Map.Entry<Integer, Cam16> entry : colorsToCam.entrySet()) {
|
||||
int color = entry.getKey();
|
||||
Cam16 cam = entry.getValue();
|
||||
|
||||
double proportion = colorsToExcitedProportion.get(color);
|
||||
double proportionScore = proportion * 100.0 * WEIGHT_PROPORTION;
|
||||
|
||||
double chromaWeight =
|
||||
cam.getChroma() < TARGET_CHROMA ? WEIGHT_CHROMA_BELOW : WEIGHT_CHROMA_ABOVE;
|
||||
double chromaScore = (cam.getChroma() - TARGET_CHROMA) * chromaWeight;
|
||||
|
||||
double score = proportionScore + chromaScore;
|
||||
colorsToScore.put(color, score);
|
||||
}
|
||||
|
||||
// Remove colors that are unsuitable, ex. very dark or unchromatic colors.
|
||||
// Also, remove colors that are very similar in hue.
|
||||
List<Integer> filteredColors = filter(colorsToExcitedProportion, colorsToCam);
|
||||
Map<Integer, Double> filteredColorsToScore = new HashMap<>();
|
||||
for (int color : filteredColors) {
|
||||
filteredColorsToScore.put(color, colorsToScore.get(color));
|
||||
}
|
||||
|
||||
// Ensure the list of colors returned is sorted such that the first in the
|
||||
// list is the most suitable, and the last is the least suitable.
|
||||
List<Map.Entry<Integer, Double>> entryList = new ArrayList<>(
|
||||
filteredColorsToScore.entrySet());
|
||||
Collections.sort(entryList, new ScoredComparator());
|
||||
List<Integer> colorsByScoreDescending = new ArrayList<>();
|
||||
for (Map.Entry<Integer, Double> entry : entryList) {
|
||||
int color = entry.getKey();
|
||||
Cam16 cam = colorsToCam.get(color);
|
||||
boolean duplicateHue = false;
|
||||
|
||||
for (Integer alreadyChosenColor : colorsByScoreDescending) {
|
||||
Cam16 alreadyChosenCam = colorsToCam.get(alreadyChosenColor);
|
||||
if (MathUtils.differenceDegrees(cam.getHue(), alreadyChosenCam.getHue()) < 15) {
|
||||
duplicateHue = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (duplicateHue) {
|
||||
continue;
|
||||
}
|
||||
colorsByScoreDescending.add(entry.getKey());
|
||||
}
|
||||
|
||||
// Ensure that at least one color is returned.
|
||||
if (colorsByScoreDescending.isEmpty()) {
|
||||
colorsByScoreDescending.add(0xff4285F4);
|
||||
}
|
||||
return colorsByScoreDescending;
|
||||
}
|
||||
|
||||
private static List<Integer> filter(
|
||||
Map<Integer, Double> colorsToExcitedProportion, Map<Integer, Cam16> colorsToCam) {
|
||||
List<Integer> filtered = new ArrayList<>();
|
||||
for (Map.Entry<Integer, Cam16> entry : colorsToCam.entrySet()) {
|
||||
int color = entry.getKey();
|
||||
Cam16 cam = entry.getValue();
|
||||
double proportion = colorsToExcitedProportion.get(color);
|
||||
|
||||
if (cam.getChroma() >= CUTOFF_CHROMA
|
||||
&& ColorUtils.lstarFromArgb(color) >= CUTOFF_TONE
|
||||
&& proportion >= CUTOFF_EXCITED_PROPORTION) {
|
||||
filtered.add(color);
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
|
||||
static class ScoredComparator implements Comparator<Map.Entry<Integer, Double>> {
|
||||
ScoredComparator() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(Map.Entry<Integer, Double> entry1, Map.Entry<Integer, Double> entry2) {
|
||||
return -entry1.getValue().compareTo(entry2.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.temperature;
|
||||
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.utils.ColorUtils;
|
||||
import com.android.systemui.monet.utils.MathUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Design utilities using color temperature theory.
|
||||
*
|
||||
* <p>Analogous colors, complementary color, and cache to efficiently, lazily, generate data for
|
||||
* calculations when needed.
|
||||
*/
|
||||
public final class TemperatureCache {
|
||||
private final Hct input;
|
||||
|
||||
private Hct precomputedComplement;
|
||||
private List<Hct> precomputedHctsByTemp;
|
||||
private List<Hct> precomputedHctsByHue;
|
||||
private Map<Hct, Double> precomputedTempsByHct;
|
||||
|
||||
private TemperatureCache() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a cache that allows calculation of ex. complementary and analogous colors.
|
||||
*
|
||||
* @param input Color to find complement/analogous colors of. Any colors will have the same
|
||||
* tone,
|
||||
* and chroma as the input color, modulo any restrictions due to the other hues
|
||||
* having lower
|
||||
* limits on chroma.
|
||||
*/
|
||||
public TemperatureCache(Hct input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
/**
|
||||
* A color that complements the input color aesthetically.
|
||||
*
|
||||
* <p>In art, this is usually described as being across the color wheel. History of this shows
|
||||
* intent as a color that is just as cool-warm as the input color is warm-cool.
|
||||
*/
|
||||
public Hct getComplement() {
|
||||
if (precomputedComplement != null) {
|
||||
return precomputedComplement;
|
||||
}
|
||||
|
||||
double coldestHue = getColdest().getHue();
|
||||
double coldestTemp = getTempsByHct().get(getColdest());
|
||||
|
||||
double warmestHue = getWarmest().getHue();
|
||||
double warmestTemp = getTempsByHct().get(getWarmest());
|
||||
double range = warmestTemp - coldestTemp;
|
||||
boolean startHueIsColdestToWarmest = isBetween(input.getHue(), coldestHue, warmestHue);
|
||||
double startHue = startHueIsColdestToWarmest ? warmestHue : coldestHue;
|
||||
double endHue = startHueIsColdestToWarmest ? coldestHue : warmestHue;
|
||||
double directionOfRotation = 1.;
|
||||
double smallestError = 1000.;
|
||||
Hct answer = getHctsByHue().get((int) Math.round(input.getHue()));
|
||||
|
||||
double complementRelativeTemp = (1. - getRelativeTemperature(input));
|
||||
// Find the color in the other section, closest to the inverse percentile
|
||||
// of the input color. This is the complement.
|
||||
for (double hueAddend = 0.; hueAddend <= 360.; hueAddend += 1.) {
|
||||
double hue = MathUtils.sanitizeDegreesDouble(
|
||||
startHue + directionOfRotation * hueAddend);
|
||||
if (!isBetween(hue, startHue, endHue)) {
|
||||
continue;
|
||||
}
|
||||
Hct possibleAnswer = getHctsByHue().get((int) Math.round(hue));
|
||||
double relativeTemp =
|
||||
(getTempsByHct().get(possibleAnswer) - coldestTemp) / range;
|
||||
double error = Math.abs(complementRelativeTemp - relativeTemp);
|
||||
if (error < smallestError) {
|
||||
smallestError = error;
|
||||
answer = possibleAnswer;
|
||||
}
|
||||
}
|
||||
precomputedComplement = answer;
|
||||
return precomputedComplement;
|
||||
}
|
||||
|
||||
/**
|
||||
* 5 colors that pair well with the input color.
|
||||
*
|
||||
* <p>The colors are equidistant in temperature and adjacent in hue.
|
||||
*/
|
||||
public List<Hct> getAnalogousColors() {
|
||||
return getAnalogousColors(5, 12);
|
||||
}
|
||||
|
||||
/**
|
||||
* A set of colors with differing hues, equidistant in temperature.
|
||||
*
|
||||
* <p>In art, this is usually described as a set of 5 colors on a color wheel divided into 12
|
||||
* sections. This method allows provision of either of those values.
|
||||
*
|
||||
* <p>Behavior is undefined when count or divisions is 0. When divisions < count, colors repeat.
|
||||
*
|
||||
* @param count The number of colors to return, includes the input color.
|
||||
* @param divisions The number of divisions on the color wheel.
|
||||
*/
|
||||
public List<Hct> getAnalogousColors(int count, int divisions) {
|
||||
// The starting hue is the hue of the input color.
|
||||
int startHue = (int) Math.round(input.getHue());
|
||||
Hct startHct = getHctsByHue().get(startHue);
|
||||
double lastTemp = getRelativeTemperature(startHct);
|
||||
|
||||
List<Hct> allColors = new ArrayList<>();
|
||||
allColors.add(startHct);
|
||||
|
||||
double absoluteTotalTempDelta = 0.f;
|
||||
for (int i = 0; i < 360; i++) {
|
||||
int hue = MathUtils.sanitizeDegreesInt(startHue + i);
|
||||
Hct hct = getHctsByHue().get(hue);
|
||||
double temp = getRelativeTemperature(hct);
|
||||
double tempDelta = Math.abs(temp - lastTemp);
|
||||
lastTemp = temp;
|
||||
absoluteTotalTempDelta += tempDelta;
|
||||
}
|
||||
|
||||
int hueAddend = 1;
|
||||
double tempStep = absoluteTotalTempDelta / (double) divisions;
|
||||
double totalTempDelta = 0.0;
|
||||
lastTemp = getRelativeTemperature(startHct);
|
||||
while (allColors.size() < divisions) {
|
||||
int hue = MathUtils.sanitizeDegreesInt(startHue + hueAddend);
|
||||
Hct hct = getHctsByHue().get(hue);
|
||||
double temp = getRelativeTemperature(hct);
|
||||
double tempDelta = Math.abs(temp - lastTemp);
|
||||
totalTempDelta += tempDelta;
|
||||
|
||||
double desiredTotalTempDeltaForIndex = (allColors.size() * tempStep);
|
||||
boolean indexSatisfied = totalTempDelta >= desiredTotalTempDeltaForIndex;
|
||||
int indexAddend = 1;
|
||||
// Keep adding this hue to the answers until its temperature is
|
||||
// insufficient. This ensures consistent behavior when there aren't
|
||||
// `divisions` discrete steps between 0 and 360 in hue with `tempStep`
|
||||
// delta in temperature between them.
|
||||
//
|
||||
// For example, white and black have no analogues: there are no other
|
||||
// colors at T100/T0. Therefore, they should just be added to the array
|
||||
// as answers.
|
||||
while (indexSatisfied && allColors.size() < divisions) {
|
||||
allColors.add(hct);
|
||||
desiredTotalTempDeltaForIndex = ((allColors.size() + indexAddend) * tempStep);
|
||||
indexSatisfied = totalTempDelta >= desiredTotalTempDeltaForIndex;
|
||||
indexAddend++;
|
||||
}
|
||||
lastTemp = temp;
|
||||
hueAddend++;
|
||||
|
||||
if (hueAddend > 360) {
|
||||
while (allColors.size() < divisions) {
|
||||
allColors.add(hct);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
List<Hct> answers = new ArrayList<>();
|
||||
answers.add(input);
|
||||
|
||||
int ccwCount = (int) Math.floor(((double) count - 1.0) / 2.0);
|
||||
for (int i = 1; i < (ccwCount + 1); i++) {
|
||||
int index = 0 - i;
|
||||
while (index < 0) {
|
||||
index = allColors.size() + index;
|
||||
}
|
||||
if (index >= allColors.size()) {
|
||||
index = index % allColors.size();
|
||||
}
|
||||
answers.add(0, allColors.get(index));
|
||||
}
|
||||
|
||||
int cwCount = count - ccwCount - 1;
|
||||
for (int i = 1; i < (cwCount + 1); i++) {
|
||||
int index = i;
|
||||
while (index < 0) {
|
||||
index = allColors.size() + index;
|
||||
}
|
||||
if (index >= allColors.size()) {
|
||||
index = index % allColors.size();
|
||||
}
|
||||
answers.add(allColors.get(index));
|
||||
}
|
||||
|
||||
return answers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Temperature relative to all colors with the same chroma and tone.
|
||||
*
|
||||
* @param hct HCT to find the relative temperature of.
|
||||
* @return Value on a scale from 0 to 1.
|
||||
*/
|
||||
public double getRelativeTemperature(Hct hct) {
|
||||
double range = getTempsByHct().get(getWarmest()) - getTempsByHct().get(getColdest());
|
||||
double differenceFromColdest =
|
||||
getTempsByHct().get(hct) - getTempsByHct().get(getColdest());
|
||||
// Handle when there's no difference in temperature between warmest and
|
||||
// coldest: for example, at T100, only one color is available, white.
|
||||
if (range == 0.) {
|
||||
return 0.5;
|
||||
}
|
||||
return differenceFromColdest / range;
|
||||
}
|
||||
|
||||
/**
|
||||
* Value representing cool-warm factor of a color. Values below 0 are considered cool, above,
|
||||
* warm.
|
||||
*
|
||||
* <p>Color science has researched emotion and harmony, which art uses to select colors.
|
||||
* Warm-cool
|
||||
* is the foundation of analogous and complementary colors. See: - Li-Chen Ou's Chapter 19 in
|
||||
* Handbook of Color Psychology (2015). - Josef Albers' Interaction of Color chapters 19 and
|
||||
* 21.
|
||||
*
|
||||
* <p>Implementation of Ou, Woodcock and Wright's algorithm, which uses Lab/LCH color space.
|
||||
* Return value has these properties:<br>
|
||||
* - Values below 0 are cool, above 0 are warm.<br>
|
||||
* - Lower bound: -9.66. Chroma is infinite. Assuming max of Lab chroma 130.<br>
|
||||
* - Upper bound: 8.61. Chroma is infinite. Assuming max of Lab chroma 130.
|
||||
*/
|
||||
public static double rawTemperature(Hct color) {
|
||||
double[] lab = ColorUtils.labFromArgb(color.toInt());
|
||||
double hue = MathUtils.sanitizeDegreesDouble(Math.toDegrees(Math.atan2(lab[2], lab[1])));
|
||||
double chroma = Math.hypot(lab[1], lab[2]);
|
||||
return -0.5
|
||||
+ 0.02
|
||||
* Math.pow(chroma, 1.07)
|
||||
* Math.cos(Math.toRadians(MathUtils.sanitizeDegreesDouble(hue - 50.)));
|
||||
}
|
||||
|
||||
/** Coldest color with same chroma and tone as input. */
|
||||
private Hct getColdest() {
|
||||
return getHctsByTemp().get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* HCTs for all colors with the same chroma/tone as the input.
|
||||
*
|
||||
* <p>Sorted by hue, ex. index 0 is hue 0.
|
||||
*/
|
||||
private List<Hct> getHctsByHue() {
|
||||
if (precomputedHctsByHue != null) {
|
||||
return precomputedHctsByHue;
|
||||
}
|
||||
List<Hct> hcts = new ArrayList<>();
|
||||
for (double hue = 0.; hue <= 360.; hue += 1.) {
|
||||
Hct colorAtHue = Hct.from(hue, input.getChroma(), input.getTone());
|
||||
hcts.add(colorAtHue);
|
||||
}
|
||||
precomputedHctsByHue = Collections.unmodifiableList(hcts);
|
||||
return precomputedHctsByHue;
|
||||
}
|
||||
|
||||
/**
|
||||
* HCTs for all colors with the same chroma/tone as the input.
|
||||
*
|
||||
* <p>Sorted from coldest first to warmest last.
|
||||
*/
|
||||
// Prevent lint for Comparator not being available on Android before API level 24, 7.0, 2016.
|
||||
// "AndroidJdkLibsChecker" for one linter, "NewApi" for another.
|
||||
// A java_library Bazel rule with an Android constraint cannot skip these warnings without this
|
||||
// annotation; another solution would be to create an android_library rule and supply
|
||||
// AndroidManifest with an SDK set higher than 23.
|
||||
@SuppressWarnings({"AndroidJdkLibsChecker", "NewApi"})
|
||||
private List<Hct> getHctsByTemp() {
|
||||
if (precomputedHctsByTemp != null) {
|
||||
return precomputedHctsByTemp;
|
||||
}
|
||||
|
||||
List<Hct> hcts = new ArrayList<>(getHctsByHue());
|
||||
hcts.add(input);
|
||||
Comparator<Hct> temperaturesComparator =
|
||||
Comparator.comparing((Hct arg) -> getTempsByHct().get(arg), Double::compareTo);
|
||||
Collections.sort(hcts, temperaturesComparator);
|
||||
precomputedHctsByTemp = hcts;
|
||||
return precomputedHctsByTemp;
|
||||
}
|
||||
|
||||
/** Keys of HCTs in getHctsByTemp, values of raw temperature. */
|
||||
private Map<Hct, Double> getTempsByHct() {
|
||||
if (precomputedTempsByHct != null) {
|
||||
return precomputedTempsByHct;
|
||||
}
|
||||
|
||||
List<Hct> allHcts = new ArrayList<>(getHctsByHue());
|
||||
allHcts.add(input);
|
||||
|
||||
Map<Hct, Double> temperaturesByHct = new HashMap<>();
|
||||
for (Hct hct : allHcts) {
|
||||
temperaturesByHct.put(hct, rawTemperature(hct));
|
||||
}
|
||||
|
||||
precomputedTempsByHct = temperaturesByHct;
|
||||
return precomputedTempsByHct;
|
||||
}
|
||||
|
||||
/** Warmest color with same chroma and tone as input. */
|
||||
private Hct getWarmest() {
|
||||
return getHctsByTemp().get(getHctsByTemp().size() - 1);
|
||||
}
|
||||
|
||||
/** Determines if an angle is between two other angles, rotating clockwise. */
|
||||
private static boolean isBetween(double angle, double a, double b) {
|
||||
if (a < b) {
|
||||
return a <= angle && angle <= b;
|
||||
}
|
||||
return a <= angle || angle <= b;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.utils;
|
||||
|
||||
/**
|
||||
* Color science utilities.
|
||||
*
|
||||
* <p>Utility methods for color science constants and color space conversions that aren't HCT or
|
||||
* CAM16.
|
||||
*/
|
||||
public class ColorUtils {
|
||||
private ColorUtils() {
|
||||
}
|
||||
|
||||
static final double[][] SRGB_TO_XYZ =
|
||||
new double[][]{
|
||||
new double[]{0.41233895, 0.35762064, 0.18051042},
|
||||
new double[]{0.2126, 0.7152, 0.0722},
|
||||
new double[]{0.01932141, 0.11916382, 0.95034478},
|
||||
};
|
||||
|
||||
static final double[][] XYZ_TO_SRGB =
|
||||
new double[][]{
|
||||
new double[]{
|
||||
3.2413774792388685, -1.5376652402851851, -0.49885366846268053,
|
||||
},
|
||||
new double[]{
|
||||
-0.9691452513005321, 1.8758853451067872, 0.04156585616912061,
|
||||
},
|
||||
new double[]{
|
||||
0.05562093689691305, -0.20395524564742123, 1.0571799111220335,
|
||||
},
|
||||
};
|
||||
|
||||
static final double[] WHITE_POINT_D65 = new double[]{95.047, 100.0, 108.883};
|
||||
|
||||
/** Converts a color from RGB components to ARGB format. */
|
||||
public static int argbFromRgb(int red, int green, int blue) {
|
||||
return (255 << 24) | ((red & 255) << 16) | ((green & 255) << 8) | (blue & 255);
|
||||
}
|
||||
|
||||
/** Converts a color from linear RGB components to ARGB format. */
|
||||
public static int argbFromLinrgb(double[] linrgb) {
|
||||
int r = delinearized(linrgb[0]);
|
||||
int g = delinearized(linrgb[1]);
|
||||
int b = delinearized(linrgb[2]);
|
||||
return argbFromRgb(r, g, b);
|
||||
}
|
||||
|
||||
/** Returns the alpha component of a color in ARGB format. */
|
||||
public static int alphaFromArgb(int argb) {
|
||||
return (argb >> 24) & 255;
|
||||
}
|
||||
|
||||
/** Returns the red component of a color in ARGB format. */
|
||||
public static int redFromArgb(int argb) {
|
||||
return (argb >> 16) & 255;
|
||||
}
|
||||
|
||||
/** Returns the green component of a color in ARGB format. */
|
||||
public static int greenFromArgb(int argb) {
|
||||
return (argb >> 8) & 255;
|
||||
}
|
||||
|
||||
/** Returns the blue component of a color in ARGB format. */
|
||||
public static int blueFromArgb(int argb) {
|
||||
return argb & 255;
|
||||
}
|
||||
|
||||
/** Returns whether a color in ARGB format is opaque. */
|
||||
public static boolean isOpaque(int argb) {
|
||||
return alphaFromArgb(argb) >= 255;
|
||||
}
|
||||
|
||||
/** Converts a color from ARGB to XYZ. */
|
||||
public static int argbFromXyz(double x, double y, double z) {
|
||||
double[][] matrix = XYZ_TO_SRGB;
|
||||
double linearR = matrix[0][0] * x + matrix[0][1] * y + matrix[0][2] * z;
|
||||
double linearG = matrix[1][0] * x + matrix[1][1] * y + matrix[1][2] * z;
|
||||
double linearB = matrix[2][0] * x + matrix[2][1] * y + matrix[2][2] * z;
|
||||
int r = delinearized(linearR);
|
||||
int g = delinearized(linearG);
|
||||
int b = delinearized(linearB);
|
||||
return argbFromRgb(r, g, b);
|
||||
}
|
||||
|
||||
/** Converts a color from XYZ to ARGB. */
|
||||
public static double[] xyzFromArgb(int argb) {
|
||||
double r = linearized(redFromArgb(argb));
|
||||
double g = linearized(greenFromArgb(argb));
|
||||
double b = linearized(blueFromArgb(argb));
|
||||
return MathUtils.matrixMultiply(new double[]{r, g, b}, SRGB_TO_XYZ);
|
||||
}
|
||||
|
||||
/** Converts a color represented in Lab color space into an ARGB integer. */
|
||||
public static int argbFromLab(double l, double a, double b) {
|
||||
double[] whitePoint = WHITE_POINT_D65;
|
||||
double fy = (l + 16.0) / 116.0;
|
||||
double fx = a / 500.0 + fy;
|
||||
double fz = fy - b / 200.0;
|
||||
double xNormalized = labInvf(fx);
|
||||
double yNormalized = labInvf(fy);
|
||||
double zNormalized = labInvf(fz);
|
||||
double x = xNormalized * whitePoint[0];
|
||||
double y = yNormalized * whitePoint[1];
|
||||
double z = zNormalized * whitePoint[2];
|
||||
return argbFromXyz(x, y, z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a color from ARGB representation to L*a*b* representation.
|
||||
*
|
||||
* @param argb the ARGB representation of a color
|
||||
* @return a Lab object representing the color
|
||||
*/
|
||||
public static double[] labFromArgb(int argb) {
|
||||
double linearR = linearized(redFromArgb(argb));
|
||||
double linearG = linearized(greenFromArgb(argb));
|
||||
double linearB = linearized(blueFromArgb(argb));
|
||||
double[][] matrix = SRGB_TO_XYZ;
|
||||
double x = matrix[0][0] * linearR + matrix[0][1] * linearG + matrix[0][2] * linearB;
|
||||
double y = matrix[1][0] * linearR + matrix[1][1] * linearG + matrix[1][2] * linearB;
|
||||
double z = matrix[2][0] * linearR + matrix[2][1] * linearG + matrix[2][2] * linearB;
|
||||
double[] whitePoint = WHITE_POINT_D65;
|
||||
double xNormalized = x / whitePoint[0];
|
||||
double yNormalized = y / whitePoint[1];
|
||||
double zNormalized = z / whitePoint[2];
|
||||
double fx = labF(xNormalized);
|
||||
double fy = labF(yNormalized);
|
||||
double fz = labF(zNormalized);
|
||||
double l = 116.0 * fy - 16;
|
||||
double a = 500.0 * (fx - fy);
|
||||
double b = 200.0 * (fy - fz);
|
||||
return new double[]{l, a, b};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an L* value to an ARGB representation.
|
||||
*
|
||||
* @param lstar L* in L*a*b*
|
||||
* @return ARGB representation of grayscale color with lightness matching L*
|
||||
*/
|
||||
public static int argbFromLstar(double lstar) {
|
||||
double y = yFromLstar(lstar);
|
||||
int component = delinearized(y);
|
||||
return argbFromRgb(component, component, component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the L* value of a color in ARGB representation.
|
||||
*
|
||||
* @param argb ARGB representation of a color
|
||||
* @return L*, from L*a*b*, coordinate of the color
|
||||
*/
|
||||
public static double lstarFromArgb(int argb) {
|
||||
double y = xyzFromArgb(argb)[1];
|
||||
return 116.0 * labF(y / 100.0) - 16.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an L* value to a Y value.
|
||||
*
|
||||
* <p>L* in L*a*b* and Y in XYZ measure the same quantity, luminance.
|
||||
*
|
||||
* <p>L* measures perceptual luminance, a linear scale. Y in XYZ measures relative luminance, a
|
||||
* logarithmic scale.
|
||||
*
|
||||
* @param lstar L* in L*a*b*
|
||||
* @return Y in XYZ
|
||||
*/
|
||||
public static double yFromLstar(double lstar) {
|
||||
return 100.0 * labInvf((lstar + 16.0) / 116.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Y value to an L* value.
|
||||
*
|
||||
* <p>L* in L*a*b* and Y in XYZ measure the same quantity, luminance.
|
||||
*
|
||||
* <p>L* measures perceptual luminance, a linear scale. Y in XYZ measures relative luminance, a
|
||||
* logarithmic scale.
|
||||
*
|
||||
* @param y Y in XYZ
|
||||
* @return L* in L*a*b*
|
||||
*/
|
||||
public static double lstarFromY(double y) {
|
||||
return labF(y / 100.0) * 116.0 - 16.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Linearizes an RGB component.
|
||||
*
|
||||
* @param rgbComponent 0 <= rgb_component <= 255, represents R/G/B channel
|
||||
* @return 0.0 <= output <= 100.0, color channel converted to linear RGB space
|
||||
*/
|
||||
public static double linearized(int rgbComponent) {
|
||||
double normalized = rgbComponent / 255.0;
|
||||
if (normalized <= 0.040449936) {
|
||||
return normalized / 12.92 * 100.0;
|
||||
} else {
|
||||
return Math.pow((normalized + 0.055) / 1.055, 2.4) * 100.0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delinearizes an RGB component.
|
||||
*
|
||||
* @param rgbComponent 0.0 <= rgb_component <= 100.0, represents linear R/G/B channel
|
||||
* @return 0 <= output <= 255, color channel converted to regular RGB space
|
||||
*/
|
||||
public static int delinearized(double rgbComponent) {
|
||||
double normalized = rgbComponent / 100.0;
|
||||
double delinearized = 0.0;
|
||||
if (normalized <= 0.0031308) {
|
||||
delinearized = normalized * 12.92;
|
||||
} else {
|
||||
delinearized = 1.055 * Math.pow(normalized, 1.0 / 2.4) - 0.055;
|
||||
}
|
||||
return MathUtils.clampInt(0, 255, (int) Math.round(delinearized * 255.0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the standard white point; white on a sunny day.
|
||||
*
|
||||
* @return The white point
|
||||
*/
|
||||
public static double[] whitePointD65() {
|
||||
return WHITE_POINT_D65;
|
||||
}
|
||||
|
||||
static double labF(double t) {
|
||||
double e = 216.0 / 24389.0;
|
||||
double kappa = 24389.0 / 27.0;
|
||||
if (t > e) {
|
||||
return Math.pow(t, 1.0 / 3.0);
|
||||
} else {
|
||||
return (kappa * t + 16) / 116;
|
||||
}
|
||||
}
|
||||
|
||||
static double labInvf(double ft) {
|
||||
double e = 216.0 / 24389.0;
|
||||
double kappa = 24389.0 / 27.0;
|
||||
double ft3 = ft * ft * ft;
|
||||
if (ft3 > e) {
|
||||
return ft3;
|
||||
} else {
|
||||
return (116 * ft - 16) / kappa;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.utils;
|
||||
|
||||
/** Utility methods for mathematical operations. */
|
||||
public class MathUtils {
|
||||
private MathUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* The signum function.
|
||||
*
|
||||
* @return 1 if num > 0, -1 if num < 0, and 0 if num = 0
|
||||
*/
|
||||
public static int signum(double num) {
|
||||
if (num < 0) {
|
||||
return -1;
|
||||
} else if (num == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The linear interpolation function.
|
||||
*
|
||||
* @return start if amount = 0 and stop if amount = 1
|
||||
*/
|
||||
public static double lerp(double start, double stop, double amount) {
|
||||
return (1.0 - amount) * start + amount * stop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clamps an integer between two integers.
|
||||
*
|
||||
* @return input when min <= input <= max, and either min or max otherwise.
|
||||
*/
|
||||
public static int clampInt(int min, int max, int input) {
|
||||
if (input < min) {
|
||||
return min;
|
||||
} else if (input > max) {
|
||||
return max;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clamps an integer between two floating-point numbers.
|
||||
*
|
||||
* @return input when min <= input <= max, and either min or max otherwise.
|
||||
*/
|
||||
public static double clampDouble(double min, double max, double input) {
|
||||
if (input < min) {
|
||||
return min;
|
||||
} else if (input > max) {
|
||||
return max;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes a degree measure as an integer.
|
||||
*
|
||||
* @return a degree measure between 0 (inclusive) and 360 (exclusive).
|
||||
*/
|
||||
public static int sanitizeDegreesInt(int degrees) {
|
||||
degrees = degrees % 360;
|
||||
if (degrees < 0) {
|
||||
degrees = degrees + 360;
|
||||
}
|
||||
return degrees;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes a degree measure as a floating-point number.
|
||||
*
|
||||
* @return a degree measure between 0.0 (inclusive) and 360.0 (exclusive).
|
||||
*/
|
||||
public static double sanitizeDegreesDouble(double degrees) {
|
||||
degrees = degrees % 360.0;
|
||||
if (degrees < 0) {
|
||||
degrees = degrees + 360.0;
|
||||
}
|
||||
return degrees;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign of direction change needed to travel from one angle to another.
|
||||
*
|
||||
* <p>For angles that are 180 degrees apart from each other, both directions have the same
|
||||
* travel
|
||||
* distance, so either direction is shortest. The value 1.0 is returned in this case.
|
||||
*
|
||||
* @param from The angle travel starts from, in degrees.
|
||||
* @param to The angle travel ends at, in degrees.
|
||||
* @return -1 if decreasing from leads to the shortest travel distance, 1 if increasing from
|
||||
* leads
|
||||
* to the shortest travel distance.
|
||||
*/
|
||||
public static double rotationDirection(double from, double to) {
|
||||
double increasingDifference = sanitizeDegreesDouble(to - from);
|
||||
return increasingDifference <= 180.0 ? 1.0 : -1.0;
|
||||
}
|
||||
|
||||
/** Distance of two points on a circle, represented using degrees. */
|
||||
public static double differenceDegrees(double a, double b) {
|
||||
return 180.0 - Math.abs(Math.abs(a - b) - 180.0);
|
||||
}
|
||||
|
||||
/** Multiplies a 1x3 row vector with a 3x3 matrix. */
|
||||
public static double[] matrixMultiply(double[] row, double[][] matrix) {
|
||||
double a = row[0] * matrix[0][0] + row[1] * matrix[0][1] + row[2] * matrix[0][2];
|
||||
double b = row[0] * matrix[1][0] + row[1] * matrix[1][1] + row[2] * matrix[1][2];
|
||||
double c = row[0] * matrix[2][0] + row[1] * matrix[2][1] + row[2] * matrix[2][2];
|
||||
return new double[]{a, b, c};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.utils;
|
||||
|
||||
/** Utility methods for string representations of colors. */
|
||||
final class StringUtils {
|
||||
private StringUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Hex string representing color, ex. #ff0000 for red.
|
||||
*
|
||||
* @param argb ARGB representation of a color.
|
||||
*/
|
||||
public static String hexFromArgb(int argb) {
|
||||
int red = ColorUtils.redFromArgb(argb);
|
||||
int blue = ColorUtils.blueFromArgb(argb);
|
||||
int green = ColorUtils.greenFromArgb(argb);
|
||||
return String.format("#%02x%02x%02x", red, green, blue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet;
|
||||
|
||||
import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.monet.blend.Blend;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4.class)
|
||||
public final class BlendTest extends SysuiTestCase {
|
||||
private static final int RED = 0xffff0000;
|
||||
private static final int BLUE = 0xff0000ff;
|
||||
private static final int GREEN = 0xff00ff00;
|
||||
private static final int YELLOW = 0xffffff00;
|
||||
|
||||
@Test
|
||||
public void harmonize_redToBlue() {
|
||||
int blended = Blend.harmonize(RED, BLUE);
|
||||
assertThat(blended).isSameColorAs(0xffFB0057);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void harmonize_redToGreen() {
|
||||
int answer = Blend.harmonize(RED, GREEN);
|
||||
assertThat(answer).isSameColorAs(0xffD85600);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void harmonize_redToYellow() {
|
||||
int answer = Blend.harmonize(RED, YELLOW);
|
||||
assertThat(answer).isSameColorAs(0xffD85600);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void harmonize_blueToGreen() {
|
||||
int answer = Blend.harmonize(BLUE, GREEN);
|
||||
assertThat(answer).isSameColorAs(0xff0047A3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void harmonize_blueToRed() {
|
||||
int answer = Blend.harmonize(BLUE, RED);
|
||||
assertThat(answer).isSameColorAs(0xff5700DC);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void harmonize_blueToYellow() {
|
||||
int answer = Blend.harmonize(BLUE, YELLOW);
|
||||
assertThat(answer).isSameColorAs(0xff0047A3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void harmonize_greenToBlue() {
|
||||
int answer = Blend.harmonize(GREEN, BLUE);
|
||||
assertThat(answer).isSameColorAs(0xff00FC94);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void harmonize_greenToRed() {
|
||||
int answer = Blend.harmonize(GREEN, RED);
|
||||
assertThat(answer).isSameColorAs(0xffB1F000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void harmonize_greenToYellow() {
|
||||
int answer = Blend.harmonize(GREEN, YELLOW);
|
||||
assertThat(answer).isSameColorAs(0xffB1F000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void harmonize_yellowToBlue() {
|
||||
int answer = Blend.harmonize(YELLOW, BLUE);
|
||||
assertThat(answer).isSameColorAs(0xffEBFFBA);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void harmonize_yellowToGreen() {
|
||||
int answer = Blend.harmonize(YELLOW, GREEN);
|
||||
assertThat(answer).isSameColorAs(0xffEBFFBA);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void harmonize_yellowToRed() {
|
||||
int answer = Blend.harmonize(YELLOW, RED);
|
||||
assertThat(answer).isSameColorAs(0xffFFF6E3);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.monet.utils.ColorUtils;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4.class)
|
||||
public final class ColorUtilsTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void argbFromLab() {
|
||||
assertThat(ColorUtils.argbFromLab(0.0, 0.0, 0.0)).isEqualTo(0xff000000);
|
||||
assertThat(ColorUtils.argbFromLab(0.25, 0.0, 0.0)).isEqualTo(0xff010101);
|
||||
assertThat(ColorUtils.argbFromLab(0.5, 0.0, 0.0)).isEqualTo(0xff020202);
|
||||
assertThat(ColorUtils.argbFromLab(0.75, 0.0, 0.0)).isEqualTo(0xff030303);
|
||||
assertThat(ColorUtils.argbFromLab(1.0, 0.0, 0.0)).isEqualTo(0xff040404);
|
||||
assertThat(ColorUtils.argbFromLab(1.5, 0.0, 0.0)).isEqualTo(0xff050505);
|
||||
assertThat(ColorUtils.argbFromLab(2.0, 0.0, 0.0)).isEqualTo(0xff070707);
|
||||
assertThat(ColorUtils.argbFromLab(3.0, 0.0, 0.0)).isEqualTo(0xff0b0b0b);
|
||||
assertThat(ColorUtils.argbFromLab(4.0, 0.0, 0.0)).isEqualTo(0xff0e0e0e);
|
||||
assertThat(ColorUtils.argbFromLab(5.0, 0.0, 0.0)).isEqualTo(0xff111111);
|
||||
assertThat(ColorUtils.argbFromLab(6.0, 0.0, 0.0)).isEqualTo(0xff131313);
|
||||
assertThat(ColorUtils.argbFromLab(7.0, 0.0, 0.0)).isEqualTo(0xff151515);
|
||||
assertThat(ColorUtils.argbFromLab(8.0, 0.0, 0.0)).isEqualTo(0xff181818);
|
||||
assertThat(ColorUtils.argbFromLab(9.0, 0.0, 0.0)).isEqualTo(0xff191919);
|
||||
assertThat(ColorUtils.argbFromLab(10.0, 0.0, 0.0)).isEqualTo(0xff1b1b1b);
|
||||
assertThat(ColorUtils.argbFromLab(20.0, 0.0, 0.0)).isEqualTo(0xff303030);
|
||||
assertThat(ColorUtils.argbFromLab(30.0, 0.0, 0.0)).isEqualTo(0xff474747);
|
||||
assertThat(ColorUtils.argbFromLab(40.0, 0.0, 0.0)).isEqualTo(0xff5e5e5e);
|
||||
assertThat(ColorUtils.argbFromLab(50.0, 0.0, 0.0)).isEqualTo(0xff777777);
|
||||
assertThat(ColorUtils.argbFromLab(60.0, 0.0, 0.0)).isEqualTo(0xff919191);
|
||||
assertThat(ColorUtils.argbFromLab(70.0, 0.0, 0.0)).isEqualTo(0xffababab);
|
||||
assertThat(ColorUtils.argbFromLab(80.0, 0.0, 0.0)).isEqualTo(0xffc6c6c6);
|
||||
assertThat(ColorUtils.argbFromLab(90.0, 0.0, 0.0)).isEqualTo(0xffe2e2e2);
|
||||
assertThat(ColorUtils.argbFromLab(100.0, 0.0, 0.0)).isEqualTo(0xffffffff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void labFromArgb() {
|
||||
assertThat(ColorUtils.labFromArgb(0xff000000)[0]).isWithin(0.001).of(0.0);
|
||||
assertThat(ColorUtils.labFromArgb(0xff010101)[0]).isWithin(0.001).of(0.2741748000656514);
|
||||
assertThat(ColorUtils.labFromArgb(0xff020202)[0]).isWithin(0.001).of(0.5483496001313029);
|
||||
assertThat(ColorUtils.labFromArgb(0xff030303)[0]).isWithin(0.001).of(0.8225244001969543);
|
||||
assertThat(ColorUtils.labFromArgb(0xff040404)[0]).isWithin(0.001).of(1.0966992002626057);
|
||||
assertThat(ColorUtils.labFromArgb(0xff050505)[0]).isWithin(0.001).of(1.3708740003282571);
|
||||
assertThat(ColorUtils.labFromArgb(0xff060606)[0]).isWithin(0.001).of(1.645048800393912);
|
||||
assertThat(ColorUtils.labFromArgb(0xff070707)[0]).isWithin(0.001).of(1.9192236004595635);
|
||||
assertThat(ColorUtils.labFromArgb(0xff080808)[0]).isWithin(0.001).of(2.193398400525215);
|
||||
assertThat(ColorUtils.labFromArgb(0xff0c0c0c)[0]).isWithin(0.001).of(3.3209754491182544);
|
||||
assertThat(ColorUtils.labFromArgb(0xff101010)[0]).isWithin(0.001).of(4.680444846419661);
|
||||
assertThat(ColorUtils.labFromArgb(0xff181818)[0]).isWithin(0.001).of(8.248186036170349);
|
||||
assertThat(ColorUtils.labFromArgb(0xff202020)[0]).isWithin(0.001).of(12.250030101522828);
|
||||
assertThat(ColorUtils.labFromArgb(0xff404040)[0]).isWithin(0.001).of(27.093413739449055);
|
||||
assertThat(ColorUtils.labFromArgb(0xff808080)[0]).isWithin(0.001).of(53.585013452169036);
|
||||
assertThat(ColorUtils.labFromArgb(0xffffffff)[0]).isWithin(0.001).of(100.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void argbFromLstar() {
|
||||
assertThat(ColorUtils.argbFromLstar(0.0)).isEqualTo(0xff000000);
|
||||
assertThat(ColorUtils.argbFromLstar(0.25)).isEqualTo(0xff010101);
|
||||
assertThat(ColorUtils.argbFromLstar(0.5)).isEqualTo(0xff020202);
|
||||
assertThat(ColorUtils.argbFromLstar(0.75)).isEqualTo(0xff030303);
|
||||
assertThat(ColorUtils.argbFromLstar(1.0)).isEqualTo(0xff040404);
|
||||
assertThat(ColorUtils.argbFromLstar(1.5)).isEqualTo(0xff050505);
|
||||
assertThat(ColorUtils.argbFromLstar(2.0)).isEqualTo(0xff070707);
|
||||
assertThat(ColorUtils.argbFromLstar(3.0)).isEqualTo(0xff0b0b0b);
|
||||
assertThat(ColorUtils.argbFromLstar(4.0)).isEqualTo(0xff0e0e0e);
|
||||
assertThat(ColorUtils.argbFromLstar(5.0)).isEqualTo(0xff111111);
|
||||
assertThat(ColorUtils.argbFromLstar(6.0)).isEqualTo(0xff131313);
|
||||
assertThat(ColorUtils.argbFromLstar(7.0)).isEqualTo(0xff151515);
|
||||
assertThat(ColorUtils.argbFromLstar(8.0)).isEqualTo(0xff181818);
|
||||
assertThat(ColorUtils.argbFromLstar(9.0)).isEqualTo(0xff191919);
|
||||
assertThat(ColorUtils.argbFromLstar(10.0)).isEqualTo(0xff1b1b1b);
|
||||
assertThat(ColorUtils.argbFromLstar(20.0)).isEqualTo(0xff303030);
|
||||
assertThat(ColorUtils.argbFromLstar(30.0)).isEqualTo(0xff474747);
|
||||
assertThat(ColorUtils.argbFromLstar(40.0)).isEqualTo(0xff5e5e5e);
|
||||
assertThat(ColorUtils.argbFromLstar(50.0)).isEqualTo(0xff777777);
|
||||
assertThat(ColorUtils.argbFromLstar(60.0)).isEqualTo(0xff919191);
|
||||
assertThat(ColorUtils.argbFromLstar(70.0)).isEqualTo(0xffababab);
|
||||
assertThat(ColorUtils.argbFromLstar(80.0)).isEqualTo(0xffc6c6c6);
|
||||
assertThat(ColorUtils.argbFromLstar(90.0)).isEqualTo(0xffe2e2e2);
|
||||
assertThat(ColorUtils.argbFromLstar(100.0)).isEqualTo(0xffffffff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lstarFromArgb() {
|
||||
assertThat(ColorUtils.lstarFromArgb(0xff000000)).isWithin(0.001).of(0.0);
|
||||
assertThat(ColorUtils.lstarFromArgb(0xff010101)).isWithin(0.001).of(0.2741748000656518);
|
||||
assertThat(ColorUtils.lstarFromArgb(0xff020202)).isWithin(0.001).of(0.5483496001313036);
|
||||
assertThat(ColorUtils.lstarFromArgb(0xff030303)).isWithin(0.001).of(0.8225244001969553);
|
||||
assertThat(ColorUtils.lstarFromArgb(0xff040404)).isWithin(0.001).of(1.0966992002626073);
|
||||
assertThat(ColorUtils.lstarFromArgb(0xff050505)).isWithin(0.001).of(1.3708740003282587);
|
||||
assertThat(ColorUtils.lstarFromArgb(0xff060606)).isWithin(0.001).of(1.6450488003939105);
|
||||
assertThat(ColorUtils.lstarFromArgb(0xff070707)).isWithin(0.001).of(1.9192236004595624);
|
||||
assertThat(ColorUtils.lstarFromArgb(0xff080808)).isWithin(0.001).of(2.1933984005252145);
|
||||
assertThat(ColorUtils.lstarFromArgb(0xff0c0c0c)).isWithin(0.001).of(3.3209754491182557);
|
||||
assertThat(ColorUtils.lstarFromArgb(0xff101010)).isWithin(0.001).of(4.680444846419663);
|
||||
assertThat(ColorUtils.lstarFromArgb(0xff181818)).isWithin(0.001).of(8.248186036170349);
|
||||
assertThat(ColorUtils.lstarFromArgb(0xff202020)).isWithin(0.001).of(12.250030101522828);
|
||||
assertThat(ColorUtils.lstarFromArgb(0xff404040)).isWithin(0.001).of(27.093413739449055);
|
||||
assertThat(ColorUtils.lstarFromArgb(0xff808080)).isWithin(0.001).of(53.585013452169036);
|
||||
assertThat(ColorUtils.lstarFromArgb(0xffffffff)).isWithin(0.001).of(100.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void yFromLstar() {
|
||||
assertThat(ColorUtils.yFromLstar(0.0)).isWithin(1e-5).of(0.0);
|
||||
assertThat(ColorUtils.yFromLstar(0.1)).isWithin(1e-5).of(0.0110705);
|
||||
assertThat(ColorUtils.yFromLstar(0.2)).isWithin(1e-5).of(0.0221411);
|
||||
assertThat(ColorUtils.yFromLstar(0.3)).isWithin(1e-5).of(0.0332116);
|
||||
assertThat(ColorUtils.yFromLstar(0.4)).isWithin(1e-5).of(0.0442822);
|
||||
assertThat(ColorUtils.yFromLstar(0.5)).isWithin(1e-5).of(0.0553528);
|
||||
assertThat(ColorUtils.yFromLstar(1.0)).isWithin(1e-5).of(0.1107056);
|
||||
assertThat(ColorUtils.yFromLstar(2.0)).isWithin(1e-5).of(0.2214112);
|
||||
assertThat(ColorUtils.yFromLstar(3.0)).isWithin(1e-5).of(0.3321169);
|
||||
assertThat(ColorUtils.yFromLstar(4.0)).isWithin(1e-5).of(0.4428225);
|
||||
assertThat(ColorUtils.yFromLstar(5.0)).isWithin(1e-5).of(0.5535282);
|
||||
assertThat(ColorUtils.yFromLstar(8.0)).isWithin(1e-5).of(0.8856451);
|
||||
assertThat(ColorUtils.yFromLstar(10.0)).isWithin(1e-5).of(1.1260199);
|
||||
assertThat(ColorUtils.yFromLstar(15.0)).isWithin(1e-5).of(1.9085832);
|
||||
assertThat(ColorUtils.yFromLstar(20.0)).isWithin(1e-5).of(2.9890524);
|
||||
assertThat(ColorUtils.yFromLstar(25.0)).isWithin(1e-5).of(4.4154767);
|
||||
assertThat(ColorUtils.yFromLstar(30.0)).isWithin(1e-5).of(6.2359055);
|
||||
assertThat(ColorUtils.yFromLstar(40.0)).isWithin(1e-5).of(11.2509737);
|
||||
assertThat(ColorUtils.yFromLstar(50.0)).isWithin(1e-5).of(18.4186518);
|
||||
assertThat(ColorUtils.yFromLstar(60.0)).isWithin(1e-5).of(28.1233342);
|
||||
assertThat(ColorUtils.yFromLstar(70.0)).isWithin(1e-5).of(40.7494157);
|
||||
assertThat(ColorUtils.yFromLstar(80.0)).isWithin(1e-5).of(56.6812907);
|
||||
assertThat(ColorUtils.yFromLstar(90.0)).isWithin(1e-5).of(76.3033539);
|
||||
assertThat(ColorUtils.yFromLstar(95.0)).isWithin(1e-5).of(87.6183294);
|
||||
assertThat(ColorUtils.yFromLstar(99.0)).isWithin(1e-5).of(97.4360239);
|
||||
assertThat(ColorUtils.yFromLstar(100.0)).isWithin(1e-5).of(100.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lstarFromY() {
|
||||
assertThat(ColorUtils.lstarFromY(0.0)).isWithin(1e-5).of(0.0);
|
||||
assertThat(ColorUtils.lstarFromY(0.1)).isWithin(1e-5).of(0.9032962);
|
||||
assertThat(ColorUtils.lstarFromY(0.2)).isWithin(1e-5).of(1.8065925);
|
||||
assertThat(ColorUtils.lstarFromY(0.3)).isWithin(1e-5).of(2.7098888);
|
||||
assertThat(ColorUtils.lstarFromY(0.4)).isWithin(1e-5).of(3.6131851);
|
||||
assertThat(ColorUtils.lstarFromY(0.5)).isWithin(1e-5).of(4.5164814);
|
||||
assertThat(ColorUtils.lstarFromY(0.8856451)).isWithin(1e-5).of(8.0);
|
||||
assertThat(ColorUtils.lstarFromY(1.0)).isWithin(1e-5).of(8.9914424);
|
||||
assertThat(ColorUtils.lstarFromY(2.0)).isWithin(1e-5).of(15.4872443);
|
||||
assertThat(ColorUtils.lstarFromY(3.0)).isWithin(1e-5).of(20.0438970);
|
||||
assertThat(ColorUtils.lstarFromY(4.0)).isWithin(1e-5).of(23.6714419);
|
||||
assertThat(ColorUtils.lstarFromY(5.0)).isWithin(1e-5).of(26.7347653);
|
||||
assertThat(ColorUtils.lstarFromY(10.0)).isWithin(1e-5).of(37.8424304);
|
||||
assertThat(ColorUtils.lstarFromY(15.0)).isWithin(1e-5).of(45.6341970);
|
||||
assertThat(ColorUtils.lstarFromY(20.0)).isWithin(1e-5).of(51.8372115);
|
||||
assertThat(ColorUtils.lstarFromY(25.0)).isWithin(1e-5).of(57.0754208);
|
||||
assertThat(ColorUtils.lstarFromY(30.0)).isWithin(1e-5).of(61.6542222);
|
||||
assertThat(ColorUtils.lstarFromY(40.0)).isWithin(1e-5).of(69.4695307);
|
||||
assertThat(ColorUtils.lstarFromY(50.0)).isWithin(1e-5).of(76.0692610);
|
||||
assertThat(ColorUtils.lstarFromY(60.0)).isWithin(1e-5).of(81.8381891);
|
||||
assertThat(ColorUtils.lstarFromY(70.0)).isWithin(1e-5).of(86.9968642);
|
||||
assertThat(ColorUtils.lstarFromY(80.0)).isWithin(1e-5).of(91.6848609);
|
||||
assertThat(ColorUtils.lstarFromY(90.0)).isWithin(1e-5).of(95.9967686);
|
||||
assertThat(ColorUtils.lstarFromY(95.0)).isWithin(1e-5).of(98.0335184);
|
||||
assertThat(ColorUtils.lstarFromY(99.0)).isWithin(1e-5).of(99.6120372);
|
||||
assertThat(ColorUtils.lstarFromY(100.0)).isWithin(1e-5).of(100.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void yToLstarToY() {
|
||||
for (double y = 0.0; y <= 100.0; y += 0.1) {
|
||||
double lstar = ColorUtils.lstarFromY(y);
|
||||
double reconstructedY = ColorUtils.yFromLstar(lstar);
|
||||
assertThat(reconstructedY).isWithin(1e-8).of(y);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lstarToYToLstar() {
|
||||
for (double lstar = 0.0; lstar <= 100.0; lstar += 0.1) {
|
||||
double y = ColorUtils.yFromLstar(lstar);
|
||||
double reconstructedLstar = ColorUtils.lstarFromY(y);
|
||||
assertThat(reconstructedLstar).isWithin(1e-8).of(lstar);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.monet.contrast.Contrast;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4.class)
|
||||
public final class ContrastTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void lighter_impossibleRatioErrors() {
|
||||
assertEquals(-1.0, Contrast.lighter(90.0, 10.0), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lighter_outOfBoundsInputAboveErrors() {
|
||||
assertEquals(-1.0, Contrast.lighter(110.0, 2.0), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lighter_outOfBoundsInputBelowErrors() {
|
||||
assertEquals(-1.0, Contrast.lighter(-10.0, 2.0), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lighterUnsafe_returnsMaxTone() {
|
||||
assertEquals(100, Contrast.lighterUnsafe(100.0, 2.0), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darker_impossibleRatioErrors() {
|
||||
assertEquals(-1.0, Contrast.darker(10.0, 20.0), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darker_outOfBoundsInputAboveErrors() {
|
||||
assertEquals(-1.0, Contrast.darker(110.0, 2.0), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darker_outOfBoundsInputBelowErrors() {
|
||||
assertEquals(-1.0, Contrast.darker(-10.0, 2.0), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkerUnsafe_returnsMinTone() {
|
||||
assertEquals(0.0, Contrast.darkerUnsafe(0.0, 2.0), 0.001);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.monet.dislike.DislikeAnalyzer;
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4.class)
|
||||
public final class DislikeTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void monkSkinToneScaleColorsLiked() {
|
||||
// From https://skintone.google#/get-started
|
||||
int[] monkSkinToneScaleColors =
|
||||
new int[]{
|
||||
0xfff6ede4,
|
||||
0xfff3e7db,
|
||||
0xfff7ead0,
|
||||
0xffeadaba,
|
||||
0xffd7bd96,
|
||||
0xffa07e56,
|
||||
0xff825c43,
|
||||
0xff604134,
|
||||
0xff3a312a,
|
||||
0xff292420,
|
||||
};
|
||||
for (int color : monkSkinToneScaleColors) {
|
||||
assertFalse(DislikeAnalyzer.isDisliked(Hct.fromInt(color)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bileColorsDisliked() {
|
||||
int[] unlikable =
|
||||
new int[]{
|
||||
0xff95884B, 0xff716B40, 0xffB08E00, 0xff4C4308, 0xff464521,
|
||||
};
|
||||
for (int color : unlikable) {
|
||||
assertTrue(DislikeAnalyzer.isDisliked(Hct.fromInt(color)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bileColorsBecameLikable() {
|
||||
int[] unlikable =
|
||||
new int[]{
|
||||
0xff95884B, 0xff716B40, 0xffB08E00, 0xff4C4308, 0xff464521,
|
||||
};
|
||||
for (int color : unlikable) {
|
||||
Hct hct = Hct.fromInt(color);
|
||||
assertTrue(DislikeAnalyzer.isDisliked(hct));
|
||||
Hct likable = DislikeAnalyzer.fixIfDisliked(hct);
|
||||
assertFalse(DislikeAnalyzer.isDisliked(likable));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet;
|
||||
|
||||
import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.monet.dynamiccolor.DynamicColor;
|
||||
import com.android.systemui.monet.dynamiccolor.ToneDeltaConstraint;
|
||||
import com.android.systemui.monet.dynamiccolor.TonePolarity;
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.scheme.SchemeTonalSpot;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4.class)
|
||||
public final class DynamicColorTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void fromArgbNoBackground_doesntChangeForContrast() {
|
||||
final int blueArgb = 0xff0000ff;
|
||||
final DynamicColor dynamicColor = DynamicColor.fromArgb(blueArgb);
|
||||
|
||||
final SchemeTonalSpot standardContrast = new SchemeTonalSpot(Hct.fromInt(blueArgb), false,
|
||||
0.0);
|
||||
assertThat(dynamicColor.getArgb(standardContrast)).isSameColorAs(blueArgb);
|
||||
|
||||
final SchemeTonalSpot minContrast = new SchemeTonalSpot(Hct.fromInt(blueArgb), false, -1.0);
|
||||
assertThat(dynamicColor.getArgb(minContrast)).isSameColorAs(blueArgb);
|
||||
|
||||
final SchemeTonalSpot maxContrast = new SchemeTonalSpot(Hct.fromInt(blueArgb), false, 1.0);
|
||||
assertThat(dynamicColor.getArgb(maxContrast)).isSameColorAs(blueArgb);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toneDeltaConstraintNoPreference_evaluatesCorrectly() {
|
||||
final int blueArgb = 0xff0000ff;
|
||||
final int redArgb = 0xffff0000;
|
||||
final DynamicColor otherDynamicColor = DynamicColor.fromArgb(redArgb);
|
||||
final DynamicColor dynamicColor =
|
||||
DynamicColor.fromArgb(
|
||||
blueArgb,
|
||||
(s) -> 30.0,
|
||||
null,
|
||||
(s) -> new ToneDeltaConstraint(30, otherDynamicColor,
|
||||
TonePolarity.NO_PREFERENCE));
|
||||
final SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(blueArgb), false, 0.0);
|
||||
assertThat(dynamicColor.getArgb(scheme)).isSameColorAs(0xff0000ef);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dynamicColor_withOpacity() {
|
||||
final DynamicColor dynamicColor =
|
||||
new DynamicColor(
|
||||
s -> 0.0,
|
||||
s -> 0.0,
|
||||
s -> s.isDark ? 100.0 : 0.0,
|
||||
s -> s.isDark ? 0.20 : 0.12,
|
||||
null,
|
||||
scheme ->
|
||||
DynamicColor.toneMinContrastDefault(
|
||||
(s) -> s.isDark ? 100.0 : 0.0, null, scheme, null),
|
||||
scheme ->
|
||||
DynamicColor.toneMaxContrastDefault(
|
||||
(s) -> s.isDark ? 100.0 : 0.0, null, scheme, null),
|
||||
null);
|
||||
final SchemeTonalSpot lightScheme = new SchemeTonalSpot(Hct.fromInt(0xff4285f4), false,
|
||||
0.0);
|
||||
assertThat(dynamicColor.getArgb(lightScheme)).isSameColorAs(0x1f000000);
|
||||
|
||||
final SchemeTonalSpot darkScheme = new SchemeTonalSpot(Hct.fromInt(0xff4285f4), true, 0.0);
|
||||
assertThat(dynamicColor.getArgb(darkScheme)).isSameColorAs(0x33ffffff);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet;
|
||||
|
||||
import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.monet.hct.Cam16;
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.hct.ViewingConditions;
|
||||
import com.android.systemui.monet.utils.ColorUtils;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4.class)
|
||||
public final class HctTest extends SysuiTestCase {
|
||||
private static final int RED = 0xffff0000;
|
||||
private static final int GREEN = 0xff00ff00;
|
||||
private static final int BLUE = 0xff0000ff;
|
||||
private static final int WHITE = 0xffffffff;
|
||||
private static final int BLACK = 0xff000000;
|
||||
private static final int MIDGRAY = 0xff777777;
|
||||
|
||||
public static final ViewingConditions VIEWING_CONDITIONS_WHITE_BACKGROUND =
|
||||
ViewingConditions.make(
|
||||
new double[]{
|
||||
ColorUtils.whitePointD65()[0],
|
||||
ColorUtils.whitePointD65()[1],
|
||||
ColorUtils.whitePointD65()[2]
|
||||
},
|
||||
(200.0 / Math.PI * ColorUtils.yFromLstar(50.0) / 100.f),
|
||||
100.0,
|
||||
2.0,
|
||||
false);
|
||||
|
||||
public static final ViewingConditions VIEWING_CONDITIONS_BLACK_BACKGROUND =
|
||||
ViewingConditions.make(
|
||||
new double[]{
|
||||
ColorUtils.whitePointD65()[0],
|
||||
ColorUtils.whitePointD65()[1],
|
||||
ColorUtils.whitePointD65()[2]
|
||||
},
|
||||
(200.0 / Math.PI * ColorUtils.yFromLstar(50.0) / 100.f),
|
||||
0.0,
|
||||
2.0,
|
||||
false);
|
||||
|
||||
@Test
|
||||
public void camFromArgb_red() {
|
||||
Cam16 cam = Cam16.fromInt(RED);
|
||||
|
||||
assertEquals(27.408, cam.getHue(), 0.001);
|
||||
assertEquals(113.357, cam.getChroma(), 0.001);
|
||||
assertEquals(46.445, cam.getJ(), 0.001);
|
||||
assertEquals(89.494, cam.getM(), 0.001);
|
||||
assertEquals(91.889, cam.getS(), 0.001);
|
||||
assertEquals(105.988, cam.getQ(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void camFromArgb_green() {
|
||||
Cam16 cam = Cam16.fromInt(GREEN);
|
||||
|
||||
assertEquals(142.139, cam.getHue(), 0.001);
|
||||
assertEquals(108.410, cam.getChroma(), 0.001);
|
||||
assertEquals(79.331, cam.getJ(), 0.001);
|
||||
assertEquals(85.587, cam.getM(), 0.001);
|
||||
assertEquals(78.604, cam.getS(), 0.001);
|
||||
assertEquals(138.520, cam.getQ(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void camFromArgb_blue() {
|
||||
Cam16 cam = Cam16.fromInt(BLUE);
|
||||
|
||||
assertEquals(282.788, cam.getHue(), 0.001);
|
||||
assertEquals(87.230, cam.getChroma(), 0.001);
|
||||
assertEquals(25.465, cam.getJ(), 0.001);
|
||||
assertEquals(68.867, cam.getM(), 0.001);
|
||||
assertEquals(93.674, cam.getS(), 0.001);
|
||||
assertEquals(78.481, cam.getQ(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void camFromArgb_white() {
|
||||
Cam16 cam = Cam16.fromInt(WHITE);
|
||||
|
||||
assertEquals(209.492, cam.getHue(), 0.001);
|
||||
assertEquals(2.869, cam.getChroma(), 0.001);
|
||||
assertEquals(100.0, cam.getJ(), 0.001);
|
||||
assertEquals(2.265, cam.getM(), 0.001);
|
||||
assertEquals(12.068, cam.getS(), 0.001);
|
||||
assertEquals(155.521, cam.getQ(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void camFromArgb_black() {
|
||||
Cam16 cam = Cam16.fromInt(BLACK);
|
||||
|
||||
assertEquals(0.0, cam.getHue(), 0.001);
|
||||
assertEquals(0.0, cam.getChroma(), 0.001);
|
||||
assertEquals(0.0, cam.getJ(), 0.001);
|
||||
assertEquals(0.0, cam.getM(), 0.001);
|
||||
assertEquals(0.0, cam.getS(), 0.001);
|
||||
assertEquals(0.0, cam.getQ(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void camToArgbToCam_red() {
|
||||
Cam16 cam = Cam16.fromInt(RED);
|
||||
int argb = cam.toInt();
|
||||
assertEquals(RED, argb);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void camToArgbToCam_green() {
|
||||
Cam16 cam = Cam16.fromInt(GREEN);
|
||||
int argb = cam.toInt();
|
||||
assertEquals(GREEN, argb);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void camToArgbToCam_blue() {
|
||||
Cam16 cam = Cam16.fromInt(BLUE);
|
||||
int argb = cam.toInt();
|
||||
assertEquals(BLUE, argb);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void viewingConditions_default() {
|
||||
ViewingConditions vc = ViewingConditions.DEFAULT;
|
||||
|
||||
assertEquals(0.184, vc.getN(), 0.001);
|
||||
assertEquals(29.981, vc.getAw(), 0.001);
|
||||
assertEquals(1.016, vc.getNbb(), 0.001);
|
||||
assertEquals(1.021, vc.getRgbD()[0], 0.001);
|
||||
assertEquals(0.986, vc.getRgbD()[1], 0.001);
|
||||
assertEquals(0.933, vc.getRgbD()[2], 0.001);
|
||||
assertEquals(0.789, vc.getFlRoot(), 0.001);
|
||||
}
|
||||
|
||||
private boolean colorIsOnBoundary(int argb) {
|
||||
return ColorUtils.redFromArgb(argb) == 0
|
||||
|| ColorUtils.redFromArgb(argb) == 255
|
||||
|| ColorUtils.greenFromArgb(argb) == 0
|
||||
|| ColorUtils.greenFromArgb(argb) == 255
|
||||
|| ColorUtils.blueFromArgb(argb) == 0
|
||||
|| ColorUtils.blueFromArgb(argb) == 255;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void correctness() {
|
||||
for (int hue = 15; hue < 360; hue += 30) {
|
||||
for (int chroma = 0; chroma <= 100; chroma += 10) {
|
||||
for (int tone = 20; tone <= 80; tone += 10) {
|
||||
Hct hctColor = Hct.from((double) hue, (double) chroma, (double) tone);
|
||||
|
||||
if (chroma > 0) {
|
||||
assertEquals(
|
||||
"Incorrect hue for H" + hue + " C" + chroma + " T" + tone,
|
||||
hue,
|
||||
hctColor.getHue(),
|
||||
4.0);
|
||||
}
|
||||
|
||||
assertTrue(
|
||||
"Negative chroma for H" + hue + " C" + chroma + " T" + tone,
|
||||
hctColor.getChroma() >= 0.0);
|
||||
|
||||
assertTrue(
|
||||
"Chroma too high for H" + hue + " C" + chroma + " T" + tone,
|
||||
hctColor.getChroma() <= chroma + 2.5);
|
||||
|
||||
if (hctColor.getChroma() < chroma - 2.5) {
|
||||
assertTrue(
|
||||
"Color not on boundary for non-sRGB color for H"
|
||||
+ hue
|
||||
+ " C"
|
||||
+ chroma
|
||||
+ " T"
|
||||
+ tone,
|
||||
colorIsOnBoundary(hctColor.toInt()));
|
||||
}
|
||||
|
||||
assertEquals(tone, hctColor.getTone(), 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void relativity_redInBlack() {
|
||||
final int colorToTest = RED;
|
||||
final Hct hct = Hct.fromInt(colorToTest);
|
||||
assertThat(0xff9F5C51)
|
||||
.isSameColorAs(
|
||||
hct.inViewingConditions(VIEWING_CONDITIONS_BLACK_BACKGROUND).toInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void relativity_redInWhite() {
|
||||
final int colorToTest = RED;
|
||||
final Hct hct = Hct.fromInt(colorToTest);
|
||||
assertThat(0xffFF5D48)
|
||||
.isSameColorAs(
|
||||
hct.inViewingConditions(VIEWING_CONDITIONS_WHITE_BACKGROUND).toInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void relativity_greenInBlack() {
|
||||
final int colorToTest = GREEN;
|
||||
final Hct hct = Hct.fromInt(colorToTest);
|
||||
assertThat(0xffACD69D)
|
||||
.isSameColorAs(
|
||||
hct.inViewingConditions(VIEWING_CONDITIONS_BLACK_BACKGROUND).toInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void relativity_greenInWhite() {
|
||||
final int colorToTest = GREEN;
|
||||
final Hct hct = Hct.fromInt(colorToTest);
|
||||
assertThat(0xff8EFF77)
|
||||
.isSameColorAs(
|
||||
hct.inViewingConditions(VIEWING_CONDITIONS_WHITE_BACKGROUND).toInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void relativity_blueInBlack() {
|
||||
final int colorToTest = BLUE;
|
||||
final Hct hct = Hct.fromInt(colorToTest);
|
||||
assertThat(0xff343654)
|
||||
.isSameColorAs(
|
||||
hct.inViewingConditions(VIEWING_CONDITIONS_BLACK_BACKGROUND).toInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void relativity_blueInWhite() {
|
||||
final int colorToTest = BLUE;
|
||||
final Hct hct = Hct.fromInt(colorToTest);
|
||||
assertThat(0xff3F49FF)
|
||||
.isSameColorAs(
|
||||
hct.inViewingConditions(VIEWING_CONDITIONS_WHITE_BACKGROUND).toInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void relativity_whiteInBlack() {
|
||||
final int colorToTest = WHITE;
|
||||
final Hct hct = Hct.fromInt(colorToTest);
|
||||
assertThat(0xffFFFFFF)
|
||||
.isSameColorAs(
|
||||
hct.inViewingConditions(VIEWING_CONDITIONS_BLACK_BACKGROUND).toInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void relativity_whiteInWhite() {
|
||||
final int colorToTest = WHITE;
|
||||
final Hct hct = Hct.fromInt(colorToTest);
|
||||
assertThat(0xffFFFFFF)
|
||||
.isSameColorAs(
|
||||
hct.inViewingConditions(VIEWING_CONDITIONS_WHITE_BACKGROUND).toInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void relativity_midgrayInBlack() {
|
||||
final int colorToTest = MIDGRAY;
|
||||
final Hct hct = Hct.fromInt(colorToTest);
|
||||
assertThat(0xff605F5F)
|
||||
.isSameColorAs(
|
||||
hct.inViewingConditions(VIEWING_CONDITIONS_BLACK_BACKGROUND).toInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void relativity_midgrayInWhite() {
|
||||
final int colorToTest = MIDGRAY;
|
||||
final Hct hct = Hct.fromInt(colorToTest);
|
||||
assertThat(0xff8E8E8E)
|
||||
.isSameColorAs(
|
||||
hct.inViewingConditions(VIEWING_CONDITIONS_WHITE_BACKGROUND).toInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void relativity_blackInBlack() {
|
||||
final int colorToTest = BLACK;
|
||||
final Hct hct = Hct.fromInt(colorToTest);
|
||||
assertThat(0xff000000)
|
||||
.isSameColorAs(
|
||||
hct.inViewingConditions(VIEWING_CONDITIONS_BLACK_BACKGROUND).toInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void relativity_blackInWhite() {
|
||||
final int colorToTest = BLACK;
|
||||
final Hct hct = Hct.fromInt(colorToTest);
|
||||
assertThat(0xff000000)
|
||||
.isSameColorAs(
|
||||
hct.inViewingConditions(VIEWING_CONDITIONS_WHITE_BACKGROUND).toInt());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.monet.utils.MathUtils;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4.class)
|
||||
public final class MathUtilsTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void signum() {
|
||||
assertThat(MathUtils.signum(1.5)).isEqualTo(1);
|
||||
assertThat(MathUtils.signum(0)).isEqualTo(0);
|
||||
assertThat(MathUtils.signum(-3.0)).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sanitizeDegreesInt() {
|
||||
assertThat(MathUtils.sanitizeDegreesInt(0)).isEqualTo(0);
|
||||
assertThat(MathUtils.sanitizeDegreesInt(30)).isEqualTo(30);
|
||||
assertThat(MathUtils.sanitizeDegreesInt(150)).isEqualTo(150);
|
||||
assertThat(MathUtils.sanitizeDegreesInt(360)).isEqualTo(0);
|
||||
assertThat(MathUtils.sanitizeDegreesInt(450)).isEqualTo(90);
|
||||
assertThat(MathUtils.sanitizeDegreesInt(1000000)).isEqualTo(280);
|
||||
assertThat(MathUtils.sanitizeDegreesInt(-10)).isEqualTo(350);
|
||||
assertThat(MathUtils.sanitizeDegreesInt(-90)).isEqualTo(270);
|
||||
assertThat(MathUtils.sanitizeDegreesInt(-1000000)).isEqualTo(80);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sanitizeDegreesDouble() {
|
||||
assertThat(MathUtils.sanitizeDegreesDouble(0.0)).isWithin(0.001).of(0.0);
|
||||
assertThat(MathUtils.sanitizeDegreesDouble(30.0)).isWithin(0.001).of(30.0);
|
||||
assertThat(MathUtils.sanitizeDegreesDouble(150.0)).isWithin(0.001).of(150.0);
|
||||
assertThat(MathUtils.sanitizeDegreesDouble(360.0)).isWithin(0.001).of(0.0);
|
||||
assertThat(MathUtils.sanitizeDegreesDouble(450.0)).isWithin(0.001).of(90.0);
|
||||
assertThat(MathUtils.sanitizeDegreesDouble(1000000.0)).isWithin(0.001).of(280.0);
|
||||
assertThat(MathUtils.sanitizeDegreesDouble(-10.0)).isWithin(0.001).of(350.0);
|
||||
assertThat(MathUtils.sanitizeDegreesDouble(-90.0)).isWithin(0.001).of(270.0);
|
||||
assertThat(MathUtils.sanitizeDegreesDouble(-1000000.0)).isWithin(0.001).of(80.0);
|
||||
assertThat(MathUtils.sanitizeDegreesDouble(100.375)).isWithin(0.001).of(100.375);
|
||||
assertThat(MathUtils.sanitizeDegreesDouble(123456.789)).isWithin(0.001).of(336.789);
|
||||
assertThat(MathUtils.sanitizeDegreesDouble(-200.625)).isWithin(0.001).of(159.375);
|
||||
assertThat(MathUtils.sanitizeDegreesDouble(-123456.789)).isWithin(0.001).of(23.211);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rotationDirection() {
|
||||
for (double from = 0.0; from < 360.0; from += 15.0) {
|
||||
for (double to = 7.5; to < 360.0; to += 15.0) {
|
||||
double expectedAnswer = originalRotationDirection(from, to);
|
||||
double actualAnswer = MathUtils.rotationDirection(from, to);
|
||||
assertThat(actualAnswer).isEqualTo(expectedAnswer);
|
||||
assertThat(Math.abs(actualAnswer)).isEqualTo(1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Original implementation for MathUtils.rotationDirection.
|
||||
// Included here to test equivalence with new implementation.
|
||||
private static double originalRotationDirection(double from, double to) {
|
||||
double a = to - from;
|
||||
double b = to - from + 360.0;
|
||||
double c = to - from - 360.0;
|
||||
double aAbs = Math.abs(a);
|
||||
double bAbs = Math.abs(b);
|
||||
double cAbs = Math.abs(c);
|
||||
if (aAbs <= bAbs && aAbs <= cAbs) {
|
||||
return a >= 0.0 ? 1.0 : -1.0;
|
||||
} else if (bAbs <= aAbs && bAbs <= cAbs) {
|
||||
return b >= 0.0 ? 1.0 : -1.0;
|
||||
} else {
|
||||
return c >= 0.0 ? 1.0 : -1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.monet.hct.Cam16;
|
||||
import com.android.systemui.monet.palettes.CorePalette;
|
||||
import com.android.systemui.monet.palettes.TonalPalette;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4.class)
|
||||
public final class PalettesTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void tones_ofBlue() {
|
||||
int color = 0xff0000ff;
|
||||
Cam16 cam = Cam16.fromInt(color);
|
||||
TonalPalette tones = TonalPalette.fromHueAndChroma(cam.getHue(), cam.getChroma());
|
||||
assertThat(tones.tone(100)).isEqualTo(0xFFFFFFFF);
|
||||
assertThat(tones.tone(95)).isEqualTo(0xfff1efff);
|
||||
assertThat(tones.tone(90)).isEqualTo(0xffe0e0ff);
|
||||
assertThat(tones.tone(80)).isEqualTo(0xffbec2ff);
|
||||
assertThat(tones.tone(70)).isEqualTo(0xff9da3ff);
|
||||
assertThat(tones.tone(60)).isEqualTo(0xff7c84ff);
|
||||
assertThat(tones.tone(50)).isEqualTo(0xff5a64ff);
|
||||
assertThat(tones.tone(40)).isEqualTo(0xff343dff);
|
||||
assertThat(tones.tone(30)).isEqualTo(0xff0000ef);
|
||||
assertThat(tones.tone(20)).isEqualTo(0xff0001ac);
|
||||
assertThat(tones.tone(10)).isEqualTo(0xff00006e);
|
||||
assertThat(tones.tone(0)).isEqualTo(0xff000000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keyTones_ofBlue() {
|
||||
CorePalette core = CorePalette.of(0xff0000ff);
|
||||
|
||||
assertThat(core.a1.tone(100)).isEqualTo(0xFFFFFFFF);
|
||||
assertThat(core.a1.tone(95)).isEqualTo(0xfff1efff);
|
||||
assertThat(core.a1.tone(90)).isEqualTo(0xffe0e0ff);
|
||||
assertThat(core.a1.tone(80)).isEqualTo(0xffbec2ff);
|
||||
assertThat(core.a1.tone(70)).isEqualTo(0xff9da3ff);
|
||||
assertThat(core.a1.tone(60)).isEqualTo(0xff7c84ff);
|
||||
assertThat(core.a1.tone(50)).isEqualTo(0xff5a64ff);
|
||||
assertThat(core.a1.tone(40)).isEqualTo(0xff343dff);
|
||||
assertThat(core.a1.tone(30)).isEqualTo(0xff0000ef);
|
||||
assertThat(core.a1.tone(20)).isEqualTo(0xff0001ac);
|
||||
assertThat(core.a1.tone(10)).isEqualTo(0xff00006e);
|
||||
assertThat(core.a1.tone(0)).isEqualTo(0xff000000);
|
||||
|
||||
assertThat(core.a2.tone(100)).isEqualTo(0xffffffff);
|
||||
assertThat(core.a2.tone(95)).isEqualTo(0xfff1efff);
|
||||
assertThat(core.a2.tone(90)).isEqualTo(0xffe1e0f9);
|
||||
assertThat(core.a2.tone(80)).isEqualTo(0xffc5c4dd);
|
||||
assertThat(core.a2.tone(70)).isEqualTo(0xffa9a9c1);
|
||||
assertThat(core.a2.tone(60)).isEqualTo(0xff8f8fa6);
|
||||
assertThat(core.a2.tone(50)).isEqualTo(0xff75758b);
|
||||
assertThat(core.a2.tone(40)).isEqualTo(0xff5c5d72);
|
||||
assertThat(core.a2.tone(30)).isEqualTo(0xff444559);
|
||||
assertThat(core.a2.tone(20)).isEqualTo(0xff2e2f42);
|
||||
assertThat(core.a2.tone(10)).isEqualTo(0xff191a2c);
|
||||
assertThat(core.a2.tone(0)).isEqualTo(0xff000000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keyTones_contentOfBlue() {
|
||||
CorePalette core = CorePalette.contentOf(0xff0000ff);
|
||||
|
||||
assertThat(core.a1.tone(100)).isEqualTo(0xffffffff);
|
||||
assertThat(core.a1.tone(95)).isEqualTo(0xfff1efff);
|
||||
assertThat(core.a1.tone(90)).isEqualTo(0xffe0e0ff);
|
||||
assertThat(core.a1.tone(80)).isEqualTo(0xffbec2ff);
|
||||
assertThat(core.a1.tone(70)).isEqualTo(0xff9da3ff);
|
||||
assertThat(core.a1.tone(60)).isEqualTo(0xff7c84ff);
|
||||
assertThat(core.a1.tone(50)).isEqualTo(0xff5a64ff);
|
||||
assertThat(core.a1.tone(40)).isEqualTo(0xff343dff);
|
||||
assertThat(core.a1.tone(30)).isEqualTo(0xff0000ef);
|
||||
assertThat(core.a1.tone(20)).isEqualTo(0xff0001ac);
|
||||
assertThat(core.a1.tone(10)).isEqualTo(0xff00006e);
|
||||
assertThat(core.a1.tone(0)).isEqualTo(0xff000000);
|
||||
|
||||
assertThat(core.a2.tone(100)).isEqualTo(0xffffffff);
|
||||
assertThat(core.a2.tone(95)).isEqualTo(0xfff1efff);
|
||||
assertThat(core.a2.tone(90)).isEqualTo(0xffe0e0ff);
|
||||
assertThat(core.a2.tone(80)).isEqualTo(0xffc1c3f4);
|
||||
assertThat(core.a2.tone(70)).isEqualTo(0xffa5a7d7);
|
||||
assertThat(core.a2.tone(60)).isEqualTo(0xff8b8dbb);
|
||||
assertThat(core.a2.tone(50)).isEqualTo(0xff7173a0);
|
||||
assertThat(core.a2.tone(40)).isEqualTo(0xff585b86);
|
||||
assertThat(core.a2.tone(30)).isEqualTo(0xff40436d);
|
||||
assertThat(core.a2.tone(20)).isEqualTo(0xff2a2d55);
|
||||
assertThat(core.a2.tone(10)).isEqualTo(0xff14173f);
|
||||
assertThat(core.a2.tone(0)).isEqualTo(0xff000000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.monet.quantize.QuantizerMap;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4.class)
|
||||
public final class QuantizerMapTest extends SysuiTestCase {
|
||||
private static final int RED = 0xffff0000;
|
||||
private static final int GREEN = 0xff00ff00;
|
||||
private static final int BLUE = 0xff0000ff;
|
||||
private static final QuantizerMap quantizer = new QuantizerMap();
|
||||
|
||||
@Test
|
||||
public void quantize_1R() {
|
||||
Map<Integer, Integer> answer = quantizer.quantize(new int[]{RED}, 128).colorToCount;
|
||||
|
||||
assertThat(answer).hasSize(1);
|
||||
assertThat(answer).containsEntry(RED, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void quantize_1G() {
|
||||
Map<Integer, Integer> answer = quantizer.quantize(new int[]{GREEN}, 128).colorToCount;
|
||||
|
||||
assertThat(answer).hasSize(1);
|
||||
assertThat(answer).containsEntry(GREEN, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void quantize_1B() {
|
||||
Map<Integer, Integer> answer = quantizer.quantize(new int[]{BLUE}, 128).colorToCount;
|
||||
|
||||
assertThat(answer).hasSize(1);
|
||||
assertThat(answer).containsEntry(BLUE, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void quantize_5B() {
|
||||
Map<Integer, Integer> answer =
|
||||
quantizer.quantize(new int[]{BLUE, BLUE, BLUE, BLUE, BLUE}, 128).colorToCount;
|
||||
|
||||
assertThat(answer).hasSize(1);
|
||||
assertThat(answer).containsEntry(BLUE, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void quantize_2R3G() {
|
||||
Map<Integer, Integer> answer =
|
||||
quantizer.quantize(new int[]{RED, RED, GREEN, GREEN, GREEN}, 128).colorToCount;
|
||||
|
||||
assertThat(answer).hasSize(2);
|
||||
assertThat(answer).containsEntry(RED, 2);
|
||||
assertThat(answer).containsEntry(GREEN, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void quantize_1R1G1B() {
|
||||
Map<Integer, Integer> answer =
|
||||
quantizer.quantize(new int[]{RED, GREEN, BLUE}, 128).colorToCount;
|
||||
|
||||
assertThat(answer).hasSize(3);
|
||||
assertThat(answer).containsEntry(RED, 1);
|
||||
assertThat(answer).containsEntry(GREEN, 1);
|
||||
assertThat(answer).containsEntry(BLUE, 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
The tests in this folder are imported from Google's internal source repository.
|
||||
|
||||
For instructions on importing, see:
|
||||
frameworks/base/packages/SystemUI/tests/src/com/android/systemui/monet/README
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet;
|
||||
|
||||
import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors;
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.scheme.SchemeContent;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4.class)
|
||||
public final class SchemeContentTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_primary() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xFF1218FF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_primary() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xFF0001C3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_primary() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xFF000181);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_primaryContainer() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFF5660FF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_primaryContainer() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFF2D36FF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_primaryContainer() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFF0000E3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_tertiaryContainer() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.tertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFFB042CC);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_tertiaryContainer() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.tertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFF9221AF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_tertiaryContainer() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.tertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFF73008E);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_objectionableTertiaryContainerLightens() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF850096), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.tertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFFD03A71);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_objectionableTertiaryContainerLightens() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF850096), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.tertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFFAC1B57);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_objectionableTertiaryContainerDarkens() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF850096), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.tertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFF870040);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_onPrimaryContainer() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFFCBCDFF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_onPrimaryContainer() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFFCECFFF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_onPrimaryContainer() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFFD6D6FF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_surface() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, -1);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xFFFBF8FF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_surface() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xFFFBF8FF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_surface() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xFFFBF8FF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_primary() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xFF5660FF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_primary() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xFFBEC2FF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_primary() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xFFF6F4FF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_primaryContainer() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFF0000E6);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_primaryContainer() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFF0000E6);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_primaryContainer() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFFC4C6FF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_onPrimaryContainer() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFF7A83FF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_onPrimaryContainer() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFFA4AAFF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_onPrimaryContainer() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFF0001C6);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_onTertiaryContainer() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFFCF60EA);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_onTertiaryContainer() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFFEB8CFF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_onTertiaryContainer() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFF63007B);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_surface() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xFF12121D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_surface() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xFF12121D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_surface() {
|
||||
SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xFF12121D);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet;
|
||||
|
||||
import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors;
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.scheme.SchemeExpressive;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4.class)
|
||||
public final class SchemeExpressiveTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_primary() {
|
||||
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xffad603c);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_primary() {
|
||||
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff924b28);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_primary() {
|
||||
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff401400);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_primaryContainer() {
|
||||
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffffdbcc);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_primaryContainer() {
|
||||
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffffdbcc);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_primaryContainer() {
|
||||
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff6f3010);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_onPrimaryContainer() {
|
||||
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff99512e);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_onPrimaryContainer() {
|
||||
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff351000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_onPrimaryContainer() {
|
||||
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffffd0bc);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_surface() {
|
||||
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_surface() {
|
||||
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_surface() {
|
||||
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_primary() {
|
||||
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xffad603c);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_primary() {
|
||||
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xffffb595);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_primary() {
|
||||
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xfffff3ee);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_primaryContainer() {
|
||||
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff743413);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_primaryContainer() {
|
||||
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff743413);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_primaryContainer() {
|
||||
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffffbb9e);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_onPrimaryContainer() {
|
||||
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xfff99f75);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_onPrimaryContainer() {
|
||||
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffffdbcc);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_onPrimaryContainer() {
|
||||
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff622706);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_surface() {
|
||||
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131a);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_surface() {
|
||||
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131a);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_surface() {
|
||||
SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131a);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet;
|
||||
|
||||
import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors;
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.scheme.SchemeFidelity;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4.class)
|
||||
public final class SchemeFidelityTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_primary() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff1218ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_primary() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff0001c3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_primary() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff000181);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_primaryContainer() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff5660ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_primaryContainer() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff2d36ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_primaryContainer() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff0000e3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_tertiaryContainer() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.tertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffd93628);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_tertiaryContainer() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.tertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffb31910);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_tertiaryContainer() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.tertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff8c0002);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_objectionableTertiaryContainerLightens() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff850096), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.tertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffbcac5a);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_objectionableTertiaryContainerLightens() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff850096), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.tertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffbcac5a);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_objectionableTertiaryContainerDarkens() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff850096), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.tertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff4d4300);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_onPrimaryContainer() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffcbcdff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_onPrimaryContainer() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffcecfff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_onPrimaryContainer() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffd6d6ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_surface() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_surface() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_surface() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_primary() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff5660ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_primary() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xffbec2ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_primary() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xfff6f4ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_primaryContainer() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff0000e6);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_primaryContainer() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff0000e6);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_primaryContainer() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffc4c6ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_onPrimaryContainer() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff7a83ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_onPrimaryContainer() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffa4aaff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_onPrimaryContainer() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff0001c6);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_onTertiaryContainer() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xfffe513e);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_onTertiaryContainer() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffFF9181);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_onTertiaryContainer() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff790001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_surface() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12121d);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_surface() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12121d);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_surface() {
|
||||
SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12121d);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet;
|
||||
|
||||
import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors;
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.scheme.SchemeFruitSalad;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4.class)
|
||||
public final class SchemeFruitSaladTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_primary() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff007ea7);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_primary() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff006688);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_primary() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff002635);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_primaryContainer() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffC2E8FF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_primaryContainer() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffC2E8FF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_primaryContainer() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff004862);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_tertiaryContainer() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.tertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffE0E0FF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_tertiaryContainer() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.tertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffE0E0FF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_tertiaryContainer() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.tertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFF3A3E74);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_onPrimaryContainer() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff006C90);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_onPrimaryContainer() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff001E2B);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_onPrimaryContainer() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffACE1FF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_surface() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_surface() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_surface() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_primary() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff007EA7);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_primary() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xFF76D1FF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_primary() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xFFECF7FF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_primaryContainer() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFF004D67);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_primaryContainer() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFF004D67);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_primaryContainer() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xFF83D5FF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_onPrimaryContainer() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff55C0F2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_onPrimaryContainer() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffC2E8FF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_onPrimaryContainer() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff003E54);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_onTertiaryContainer() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffADB0EF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_onTertiaryContainer() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffe0e0ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_onTertiaryContainer() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff30346A);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_surface() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131c);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_surface() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131c);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_surface() {
|
||||
SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131c);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet;
|
||||
|
||||
import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors;
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.scheme.SchemeMonochrome;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4.class)
|
||||
public final class SchemeMonochromeTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_primary() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff747474);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_primary() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff5e5e5e);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_primary() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff222222);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_primaryContainer() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffe2e2e2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_primaryContainer() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffe2e2e2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_primaryContainer() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff434343);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_onPrimaryContainer() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff646464);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_onPrimaryContainer() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff1b1b1b);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_onPrimaryContainer() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffdadada);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_surface() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfff9f9f9);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_surface() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfff9f9f9);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_surface() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfff9f9f9);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_primary() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff747474);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_primary() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xffc6c6c6);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_primary() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xfff5f5f5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_primaryContainer() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff474747);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_primaryContainer() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff474747);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_primaryContainer() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffcbcbcb);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_onPrimaryContainer() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffb5b5b5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_onPrimaryContainer() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffe2e2e2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_onPrimaryContainer() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff393939);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_onTertiaryContainer() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffb5b5b5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_onTertiaryContainer() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffe2e2e2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_onTertiaryContainer() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff393939);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_surface() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131313);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_surface() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131313);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_surface() {
|
||||
SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131313);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet;
|
||||
|
||||
import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors;
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.scheme.SchemeNeutral;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4.class)
|
||||
public final class SchemeNeutralTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_primary() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff737383);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_primary() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff5d5d6c);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_primary() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff21212e);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_primaryContainer() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffe2e1f3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_primaryContainer() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffe2e1f3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_primaryContainer() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff414250);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_onPrimaryContainer() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff636372);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_onPrimaryContainer() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff1a1b27);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_onPrimaryContainer() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffd9d8ea);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_surface() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffcf8fa);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_surface() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffcf8fa);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_surface() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffcf8fa);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_primary() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff737383);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_primary() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xffc6c5d6);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_primary() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xfff6f4ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_primaryContainer() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff454654);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_primaryContainer() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff454654);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_primaryContainer() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffcac9da);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_onPrimaryContainer() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffb5b3c4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_onPrimaryContainer() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffe2e1f3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_onPrimaryContainer() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff373846);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_onTertiaryContainer() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffb3b3cb);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_onTertiaryContainer() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffe1e0f9);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_onTertiaryContainer() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff37374b);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_surface() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131315);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_surface() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131315);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_surface() {
|
||||
SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131315);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet;
|
||||
|
||||
import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors;
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.scheme.SchemeRainbow;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4.class)
|
||||
public final class SchemeRainbowTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_primary() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff676DC1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_primary() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff5056A9);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_primary() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff0F136A);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_primaryContainer() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffE0E0FF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_primaryContainer() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffE0E0FF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_primaryContainer() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff34398B);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_tertiaryContainer() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.tertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffffd8ee);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_tertiaryContainer() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.tertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffffd8ee);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_tertiaryContainer() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.tertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff5A384E);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_onPrimaryContainer() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff565CB0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_onPrimaryContainer() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff050865);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_onPrimaryContainer() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffd6d6ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_surface() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfff9f9f9);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_surface() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfff9f9f9);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_surface() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfff9f9f9);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_primary() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff676DC1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_primary() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xffbec2ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_primary() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xfff6f4ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_primaryContainer() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff383E8F);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_primaryContainer() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff383E8F);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_primaryContainer() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffc4c6ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_onPrimaryContainer() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffa9afff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_onPrimaryContainer() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffe0e0ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_onPrimaryContainer() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff292f81);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_onTertiaryContainer() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffd5a8c3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_onTertiaryContainer() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffffd8ee);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_onTertiaryContainer() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff4f2e44);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_surface() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131313);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_surface() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131313);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_surface() {
|
||||
SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131313);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet;
|
||||
|
||||
import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.monet.scheme.Scheme;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4.class)
|
||||
public final class SchemeTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void lightScheme_ofBlue() {
|
||||
Scheme scheme = Scheme.light(0xff0000ff);
|
||||
assertThat(scheme.getPrimary()).isSameColorAs(0xff343DFF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkScheme_ofBlue() {
|
||||
Scheme scheme = Scheme.dark(0xff0000ff);
|
||||
assertThat(scheme.getPrimary()).isSameColorAs(0xffBEC2FF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void thirdPartyScheme_light() {
|
||||
Scheme scheme = Scheme.light(0xff6750A4);
|
||||
assertThat(scheme.getPrimary()).isSameColorAs(0xff6750A4);
|
||||
assertThat(scheme.getSecondary()).isSameColorAs(0xff625B71);
|
||||
assertThat(scheme.getTertiary()).isSameColorAs(0xff7e5260);
|
||||
assertThat(scheme.getSurface()).isSameColorAs(0xfffffbff);
|
||||
assertThat(scheme.getOnSurface()).isSameColorAs(0xff1c1b1e);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void thirdPartyScheme_dark() {
|
||||
Scheme scheme = Scheme.dark(0xff6750A4);
|
||||
assertThat(scheme.getPrimary()).isSameColorAs(0xffcfbcff);
|
||||
assertThat(scheme.getSecondary()).isSameColorAs(0xffcbc2db);
|
||||
assertThat(scheme.getTertiary()).isSameColorAs(0xffefb8c8);
|
||||
assertThat(scheme.getSurface()).isSameColorAs(0xff1c1b1e);
|
||||
assertThat(scheme.getOnSurface()).isSameColorAs(0xffe6e1e6);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightSchemeFromHighChromaColor() {
|
||||
Scheme scheme = Scheme.light(0xfffa2bec);
|
||||
assertThat(scheme.getPrimary()).isSameColorAs(0xffab00a2);
|
||||
assertThat(scheme.getOnPrimary()).isSameColorAs(0xffffffff);
|
||||
assertThat(scheme.getPrimaryContainer()).isSameColorAs(0xffffd7f3);
|
||||
assertThat(scheme.getOnPrimaryContainer()).isSameColorAs(0xff390035);
|
||||
assertThat(scheme.getSecondary()).isSameColorAs(0xff6e5868);
|
||||
assertThat(scheme.getOnSecondary()).isSameColorAs(0xffffffff);
|
||||
assertThat(scheme.getSecondaryContainer()).isSameColorAs(0xfff8daee);
|
||||
assertThat(scheme.getOnSecondaryContainer()).isSameColorAs(0xff271624);
|
||||
assertThat(scheme.getTertiary()).isSameColorAs(0xff815343);
|
||||
assertThat(scheme.getOnTertiary()).isSameColorAs(0xffffffff);
|
||||
assertThat(scheme.getTertiaryContainer()).isSameColorAs(0xffffdbd0);
|
||||
assertThat(scheme.getOnTertiaryContainer()).isSameColorAs(0xff321207);
|
||||
assertThat(scheme.getError()).isSameColorAs(0xffba1a1a);
|
||||
assertThat(scheme.getOnError()).isSameColorAs(0xffffffff);
|
||||
assertThat(scheme.getErrorContainer()).isSameColorAs(0xffffdad6);
|
||||
assertThat(scheme.getOnErrorContainer()).isSameColorAs(0xff410002);
|
||||
assertThat(scheme.getBackground()).isSameColorAs(0xfffffbff);
|
||||
assertThat(scheme.getOnBackground()).isSameColorAs(0xff1f1a1d);
|
||||
assertThat(scheme.getSurface()).isSameColorAs(0xfffffbff);
|
||||
assertThat(scheme.getOnSurface()).isSameColorAs(0xff1f1a1d);
|
||||
assertThat(scheme.getSurfaceVariant()).isSameColorAs(0xffeedee7);
|
||||
assertThat(scheme.getOnSurfaceVariant()).isSameColorAs(0xff4e444b);
|
||||
assertThat(scheme.getOutline()).isSameColorAs(0xff80747b);
|
||||
assertThat(scheme.getOutlineVariant()).isSameColorAs(0xffd2c2cb);
|
||||
assertThat(scheme.getShadow()).isSameColorAs(0xff000000);
|
||||
assertThat(scheme.getScrim()).isSameColorAs(0xff000000);
|
||||
assertThat(scheme.getInverseSurface()).isSameColorAs(0xff342f32);
|
||||
assertThat(scheme.getInverseOnSurface()).isSameColorAs(0xfff8eef2);
|
||||
assertThat(scheme.getInversePrimary()).isSameColorAs(0xffffabee);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkSchemeFromHighChromaColor() {
|
||||
Scheme scheme = Scheme.dark(0xfffa2bec);
|
||||
assertThat(scheme.getPrimary()).isSameColorAs(0xffffabee);
|
||||
assertThat(scheme.getOnPrimary()).isSameColorAs(0xff5c0057);
|
||||
assertThat(scheme.getPrimaryContainer()).isSameColorAs(0xff83007b);
|
||||
assertThat(scheme.getOnPrimaryContainer()).isSameColorAs(0xffffd7f3);
|
||||
assertThat(scheme.getSecondary()).isSameColorAs(0xffdbbed1);
|
||||
assertThat(scheme.getOnSecondary()).isSameColorAs(0xff3e2a39);
|
||||
assertThat(scheme.getSecondaryContainer()).isSameColorAs(0xff554050);
|
||||
assertThat(scheme.getOnSecondaryContainer()).isSameColorAs(0xfff8daee);
|
||||
assertThat(scheme.getTertiary()).isSameColorAs(0xfff5b9a5);
|
||||
assertThat(scheme.getOnTertiary()).isSameColorAs(0xff4c2619);
|
||||
assertThat(scheme.getTertiaryContainer()).isSameColorAs(0xff663c2d);
|
||||
assertThat(scheme.getOnTertiaryContainer()).isSameColorAs(0xffffdbd0);
|
||||
assertThat(scheme.getError()).isSameColorAs(0xffffb4ab);
|
||||
assertThat(scheme.getOnError()).isSameColorAs(0xff690005);
|
||||
assertThat(scheme.getErrorContainer()).isSameColorAs(0xff93000a);
|
||||
assertThat(scheme.getOnErrorContainer()).isSameColorAs(0xffffb4ab);
|
||||
assertThat(scheme.getBackground()).isSameColorAs(0xff1f1a1d);
|
||||
assertThat(scheme.getOnBackground()).isSameColorAs(0xffeae0e4);
|
||||
assertThat(scheme.getSurface()).isSameColorAs(0xff1f1a1d);
|
||||
assertThat(scheme.getOnSurface()).isSameColorAs(0xffeae0e4);
|
||||
assertThat(scheme.getSurfaceVariant()).isSameColorAs(0xff4e444b);
|
||||
assertThat(scheme.getOnSurfaceVariant()).isSameColorAs(0xffd2c2cb);
|
||||
assertThat(scheme.getOutline()).isSameColorAs(0xff9a8d95);
|
||||
assertThat(scheme.getOutlineVariant()).isSameColorAs(0xff4e444b);
|
||||
assertThat(scheme.getShadow()).isSameColorAs(0xff000000);
|
||||
assertThat(scheme.getScrim()).isSameColorAs(0xff000000);
|
||||
assertThat(scheme.getInverseSurface()).isSameColorAs(0xffeae0e4);
|
||||
assertThat(scheme.getInverseOnSurface()).isSameColorAs(0xff342f32);
|
||||
assertThat(scheme.getInversePrimary()).isSameColorAs(0xffab00a2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightContentSchemeFromHighChromaColor() {
|
||||
Scheme scheme = Scheme.lightContent(0xfffa2bec);
|
||||
assertThat(scheme.getPrimary()).isSameColorAs(0xffab00a2);
|
||||
assertThat(scheme.getOnPrimary()).isSameColorAs(0xffffffff);
|
||||
assertThat(scheme.getPrimaryContainer()).isSameColorAs(0xffffd7f3);
|
||||
assertThat(scheme.getOnPrimaryContainer()).isSameColorAs(0xff390035);
|
||||
assertThat(scheme.getSecondary()).isSameColorAs(0xff7f4e75);
|
||||
assertThat(scheme.getOnSecondary()).isSameColorAs(0xffffffff);
|
||||
assertThat(scheme.getSecondaryContainer()).isSameColorAs(0xffffd7f3);
|
||||
assertThat(scheme.getOnSecondaryContainer()).isSameColorAs(0xff330b2f);
|
||||
assertThat(scheme.getTertiary()).isSameColorAs(0xff9c4323);
|
||||
assertThat(scheme.getOnTertiary()).isSameColorAs(0xffffffff);
|
||||
assertThat(scheme.getTertiaryContainer()).isSameColorAs(0xffffdbd0);
|
||||
assertThat(scheme.getOnTertiaryContainer()).isSameColorAs(0xff390c00);
|
||||
assertThat(scheme.getError()).isSameColorAs(0xffba1a1a);
|
||||
assertThat(scheme.getOnError()).isSameColorAs(0xffffffff);
|
||||
assertThat(scheme.getErrorContainer()).isSameColorAs(0xffffdad6);
|
||||
assertThat(scheme.getOnErrorContainer()).isSameColorAs(0xff410002);
|
||||
assertThat(scheme.getBackground()).isSameColorAs(0xfffffbff);
|
||||
assertThat(scheme.getOnBackground()).isSameColorAs(0xff1f1a1d);
|
||||
assertThat(scheme.getSurface()).isSameColorAs(0xfffffbff);
|
||||
assertThat(scheme.getOnSurface()).isSameColorAs(0xff1f1a1d);
|
||||
assertThat(scheme.getSurfaceVariant()).isSameColorAs(0xffeedee7);
|
||||
assertThat(scheme.getOnSurfaceVariant()).isSameColorAs(0xff4e444b);
|
||||
assertThat(scheme.getOutline()).isSameColorAs(0xff80747b);
|
||||
assertThat(scheme.getOutlineVariant()).isSameColorAs(0xffd2c2cb);
|
||||
assertThat(scheme.getShadow()).isSameColorAs(0xff000000);
|
||||
assertThat(scheme.getScrim()).isSameColorAs(0xff000000);
|
||||
assertThat(scheme.getInverseSurface()).isSameColorAs(0xff342f32);
|
||||
assertThat(scheme.getInverseOnSurface()).isSameColorAs(0xfff8eef2);
|
||||
assertThat(scheme.getInversePrimary()).isSameColorAs(0xffffabee);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkContentSchemeFromHighChromaColor() {
|
||||
Scheme scheme = Scheme.darkContent(0xfffa2bec);
|
||||
assertThat(scheme.getPrimary()).isSameColorAs(0xffffabee);
|
||||
assertThat(scheme.getOnPrimary()).isSameColorAs(0xff5c0057);
|
||||
assertThat(scheme.getPrimaryContainer()).isSameColorAs(0xff83007b);
|
||||
assertThat(scheme.getOnPrimaryContainer()).isSameColorAs(0xffffd7f3);
|
||||
assertThat(scheme.getSecondary()).isSameColorAs(0xfff0b4e1);
|
||||
assertThat(scheme.getOnSecondary()).isSameColorAs(0xff4b2145);
|
||||
assertThat(scheme.getSecondaryContainer()).isSameColorAs(0xff64375c);
|
||||
assertThat(scheme.getOnSecondaryContainer()).isSameColorAs(0xffffd7f3);
|
||||
assertThat(scheme.getTertiary()).isSameColorAs(0xffffb59c);
|
||||
assertThat(scheme.getOnTertiary()).isSameColorAs(0xff5c1900);
|
||||
assertThat(scheme.getTertiaryContainer()).isSameColorAs(0xff7d2c0d);
|
||||
assertThat(scheme.getOnTertiaryContainer()).isSameColorAs(0xffffdbd0);
|
||||
assertThat(scheme.getError()).isSameColorAs(0xffffb4ab);
|
||||
assertThat(scheme.getOnError()).isSameColorAs(0xff690005);
|
||||
assertThat(scheme.getErrorContainer()).isSameColorAs(0xff93000a);
|
||||
assertThat(scheme.getOnErrorContainer()).isSameColorAs(0xffffb4ab);
|
||||
assertThat(scheme.getBackground()).isSameColorAs(0xff1f1a1d);
|
||||
assertThat(scheme.getOnBackground()).isSameColorAs(0xffeae0e4);
|
||||
assertThat(scheme.getSurface()).isSameColorAs(0xff1f1a1d);
|
||||
assertThat(scheme.getOnSurface()).isSameColorAs(0xffeae0e4);
|
||||
assertThat(scheme.getSurfaceVariant()).isSameColorAs(0xff4e444b);
|
||||
assertThat(scheme.getOnSurfaceVariant()).isSameColorAs(0xffd2c2cb);
|
||||
assertThat(scheme.getOutline()).isSameColorAs(0xff9a8d95);
|
||||
assertThat(scheme.getOutlineVariant()).isSameColorAs(0xff4e444b);
|
||||
assertThat(scheme.getShadow()).isSameColorAs(0xff000000);
|
||||
assertThat(scheme.getScrim()).isSameColorAs(0xff000000);
|
||||
assertThat(scheme.getInverseSurface()).isSameColorAs(0xffeae0e4);
|
||||
assertThat(scheme.getInverseOnSurface()).isSameColorAs(0xff342f32);
|
||||
assertThat(scheme.getInversePrimary()).isSameColorAs(0xffab00a2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet;
|
||||
|
||||
import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors;
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.scheme.SchemeTonalSpot;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4.class)
|
||||
public final class SchemeTonalSpotTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_primary() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff6a6fb1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_primary() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff545999);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_primary() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff161a59);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_primaryContainer() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffe0e0ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_primaryContainer() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffe0e0ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_primaryContainer() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff383c7c);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_onPrimaryContainer() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff5a5fa0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_onPrimaryContainer() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff0e1253);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_onPrimaryContainer() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffd6d6ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_surface() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_surface() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_surface() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_onSurface() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xff5f5e65);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_onSurface() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xff1b1b21);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_onSurface() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xff1a1a20);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_onSecondary() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.onSecondary.getArgb(scheme)).isSameColorAs(0xffcfcfe7);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_onSecondary() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.onSecondary.getArgb(scheme)).isSameColorAs(0xffffffff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_onSecondary() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.onSecondary.getArgb(scheme)).isSameColorAs(0xffababc3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_onTertiary() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.onTertiary.getArgb(scheme)).isSameColorAs(0xfff3c3df);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_onTertiary() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.onTertiary.getArgb(scheme)).isSameColorAs(0xffffffff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_onTertiary() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.onTertiary.getArgb(scheme)).isSameColorAs(0xffcda0bb);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_onError() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.onError.getArgb(scheme)).isSameColorAs(0xffffc2bb);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_onError() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.onError.getArgb(scheme)).isSameColorAs(0xffffffff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_onError() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.onError.getArgb(scheme)).isSameColorAs(0xffff8d80);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_primary() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff6a6fb1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_primary() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xffbec2ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_primary() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xfff6f4ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_primaryContainer() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff3c4180);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_primaryContainer() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff3c4180);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_primaryContainer() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffc4c6ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_onPrimaryContainer() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffabaff7);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_onPrimaryContainer() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffe0e0ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_onPrimaryContainer() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff2e3271);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_onTertiaryContainer() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffd5a8c3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_onTertiaryContainer() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffffd8ee);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_onTertiaryContainer() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff4f2e44);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_onSecondary() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.onSecondary.getArgb(scheme)).isSameColorAs(0xfffffbff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_onSecondary() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.onSecondary.getArgb(scheme)).isSameColorAs(0xff2e2f42);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_onSecondary() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.onSecondary.getArgb(scheme)).isSameColorAs(0xff505165);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_onTertiary() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.onTertiary.getArgb(scheme)).isSameColorAs(0xfffffbff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_onTertiary() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.onTertiary.getArgb(scheme)).isSameColorAs(0xff46263b);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_onTertiary() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.onTertiary.getArgb(scheme)).isSameColorAs(0xff6b485f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_onError() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.onError.getArgb(scheme)).isSameColorAs(0xfffffbff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_onError() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.onError.getArgb(scheme)).isSameColorAs(0xff690005);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_onError() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.onError.getArgb(scheme)).isSameColorAs(0xffa80710);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_surface() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131318);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_surface() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131318);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_surface() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff131318);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_onSurface() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xffa4a2a9);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_onSurface() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xffe4e1e9);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_onSurface() {
|
||||
SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.onSurface.getArgb(scheme)).isSameColorAs(0xffe5e2ea);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet;
|
||||
|
||||
import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors;
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.scheme.SchemeVibrant;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4.class)
|
||||
public final class SchemeVibrantTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_primary() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff5660ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_primary() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff343dff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_primary() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff000181);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_primaryContainer() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffe0e0ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_primaryContainer() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffe0e0ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_primaryContainer() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff0000e3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_onPrimaryContainer() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff3e47ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_onPrimaryContainer() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff00006e);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_onPrimaryContainer() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffd6d6ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_minContrast_surface() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, -1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_standardContrast_surface() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, 0.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lightTheme_maxContrast_surface() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, 1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xfffbf8ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_primary() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xff5660ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_primary() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xffbec2ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_primary() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.primary.getArgb(scheme)).isSameColorAs(0xfff6f4ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_primaryContainer() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff0000ef);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_primaryContainer() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff0000ef);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_primaryContainer() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.primaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffc4c6ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_onPrimaryContainer() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffa9afff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_onPrimaryContainer() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffe0e0ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_onPrimaryContainer() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff0001c6);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_onTertiaryContainer() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xffc9a9df);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_onTertiaryContainer() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xfff2daff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_onTertiaryContainer() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme)).isSameColorAs(
|
||||
0xff472e5b);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_minContrast_surface() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, -1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131a);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_standardContrast_surface() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, 0.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131a);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void darkTheme_maxContrast_surface() {
|
||||
SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, 1.0);
|
||||
assertThat(MaterialDynamicColors.surface.getArgb(scheme)).isSameColorAs(0xff12131a);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.monet.score.Score;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4.class)
|
||||
public final class ScoreTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void score_picksMostChromatic() {
|
||||
Map<Integer, Integer> colorsToPopulation = new HashMap<>();
|
||||
colorsToPopulation.put(0xFF0000FF, 1);
|
||||
colorsToPopulation.put(0xFFFF0000, 1);
|
||||
colorsToPopulation.put(0xFF00FF00, 1);
|
||||
|
||||
List<Integer> scores = Score.score(colorsToPopulation);
|
||||
|
||||
assertThat(scores.get(0)).isEqualTo(0xffff0000);
|
||||
assertThat(scores.get(1)).isEqualTo(0xff00ff00);
|
||||
assertThat(scores.get(2)).isEqualTo(0xff0000ff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void score_usesGblueFallback() {
|
||||
Map<Integer, Integer> colorsToPopulation = new HashMap<>();
|
||||
colorsToPopulation.put(0xFF000000, 1);
|
||||
|
||||
List<Integer> scores = Score.score(colorsToPopulation);
|
||||
|
||||
assertThat(scores).containsExactly(0xff4285f4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void score_dedupesNearbyHues() {
|
||||
Map<Integer, Integer> colorsToPopulation = new HashMap<>();
|
||||
colorsToPopulation.put(0xff008772, 1);
|
||||
colorsToPopulation.put(0xff318477, 1);
|
||||
|
||||
List<Integer> scores = Score.score(colorsToPopulation);
|
||||
|
||||
assertThat(scores).containsExactly(0xff008772);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet;
|
||||
|
||||
import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.monet.hct.Hct;
|
||||
import com.android.systemui.monet.temperature.TemperatureCache;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4.class)
|
||||
public final class TemperatureCacheTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void testRawTemperature() {
|
||||
final Hct blueHct = Hct.fromInt(0xff0000ff);
|
||||
final double blueTemp = TemperatureCache.rawTemperature(blueHct);
|
||||
assertEquals(-1.393, blueTemp, 0.001);
|
||||
|
||||
final Hct redHct = Hct.fromInt(0xffff0000);
|
||||
final double redTemp = TemperatureCache.rawTemperature(redHct);
|
||||
assertEquals(2.351, redTemp, 0.001);
|
||||
|
||||
final Hct greenHct = Hct.fromInt(0xff00ff00);
|
||||
final double greenTemp = TemperatureCache.rawTemperature(greenHct);
|
||||
assertEquals(-0.267, greenTemp, 0.001);
|
||||
|
||||
final Hct whiteHct = Hct.fromInt(0xffffffff);
|
||||
final double whiteTemp = TemperatureCache.rawTemperature(whiteHct);
|
||||
assertEquals(-0.5, whiteTemp, 0.001);
|
||||
|
||||
final Hct blackHct = Hct.fromInt(0xff000000);
|
||||
final double blackTemp = TemperatureCache.rawTemperature(blackHct);
|
||||
assertEquals(-0.5, blackTemp, 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComplement() {
|
||||
final int blueComplement =
|
||||
new TemperatureCache(Hct.fromInt(0xff0000ff)).getComplement().toInt();
|
||||
assertThat(0xff9D0002).isSameColorAs(blueComplement);
|
||||
|
||||
final int redComplement = new TemperatureCache(
|
||||
Hct.fromInt(0xffff0000)).getComplement().toInt();
|
||||
assertThat(0xff007BFC).isSameColorAs(redComplement);
|
||||
|
||||
final int greenComplement =
|
||||
new TemperatureCache(Hct.fromInt(0xff00ff00)).getComplement().toInt();
|
||||
assertThat(0xffFFD2C9).isSameColorAs(greenComplement);
|
||||
|
||||
final int whiteComplement =
|
||||
new TemperatureCache(Hct.fromInt(0xffffffff)).getComplement().toInt();
|
||||
assertThat(0xffffffff).isSameColorAs(whiteComplement);
|
||||
|
||||
final int blackComplement =
|
||||
new TemperatureCache(Hct.fromInt(0xff000000)).getComplement().toInt();
|
||||
assertThat(0xff000000).isSameColorAs(blackComplement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAnalogous() {
|
||||
final List<Hct> blueAnalogous =
|
||||
new TemperatureCache(Hct.fromInt(0xff0000ff)).getAnalogousColors();
|
||||
assertThat(0xff00590C).isSameColorAs(blueAnalogous.get(0).toInt());
|
||||
assertThat(0xff00564E).isSameColorAs(blueAnalogous.get(1).toInt());
|
||||
assertThat(0xff0000ff).isSameColorAs(blueAnalogous.get(2).toInt());
|
||||
assertThat(0xff6700CC).isSameColorAs(blueAnalogous.get(3).toInt());
|
||||
assertThat(0xff81009F).isSameColorAs(blueAnalogous.get(4).toInt());
|
||||
|
||||
final List<Hct> redAnalogous =
|
||||
new TemperatureCache(Hct.fromInt(0xffff0000)).getAnalogousColors();
|
||||
assertThat(0xffF60082).isSameColorAs(redAnalogous.get(0).toInt());
|
||||
assertThat(0xffFC004C).isSameColorAs(redAnalogous.get(1).toInt());
|
||||
assertThat(0xffff0000).isSameColorAs(redAnalogous.get(2).toInt());
|
||||
assertThat(0xffD95500).isSameColorAs(redAnalogous.get(3).toInt());
|
||||
assertThat(0xffAF7200).isSameColorAs(redAnalogous.get(4).toInt());
|
||||
|
||||
final List<Hct> greenAnalogous =
|
||||
new TemperatureCache(Hct.fromInt(0xff00ff00)).getAnalogousColors();
|
||||
assertThat(0xffCEE900).isSameColorAs(greenAnalogous.get(0).toInt());
|
||||
assertThat(0xff92F500).isSameColorAs(greenAnalogous.get(1).toInt());
|
||||
assertThat(0xff00ff00).isSameColorAs(greenAnalogous.get(2).toInt());
|
||||
assertThat(0xff00FD6F).isSameColorAs(greenAnalogous.get(3).toInt());
|
||||
assertThat(0xff00FAB3).isSameColorAs(greenAnalogous.get(4).toInt());
|
||||
|
||||
final List<Hct> blackAnalogous =
|
||||
new TemperatureCache(Hct.fromInt(0xff000000)).getAnalogousColors();
|
||||
assertThat(0xff000000).isSameColorAs(blackAnalogous.get(0).toInt());
|
||||
assertThat(0xff000000).isSameColorAs(blackAnalogous.get(1).toInt());
|
||||
assertThat(0xff000000).isSameColorAs(blackAnalogous.get(2).toInt());
|
||||
assertThat(0xff000000).isSameColorAs(blackAnalogous.get(3).toInt());
|
||||
assertThat(0xff000000).isSameColorAs(blackAnalogous.get(4).toInt());
|
||||
|
||||
final List<Hct> whiteAnalogous =
|
||||
new TemperatureCache(Hct.fromInt(0xffffffff)).getAnalogousColors();
|
||||
assertThat(0xffffffff).isSameColorAs(whiteAnalogous.get(0).toInt());
|
||||
assertThat(0xffffffff).isSameColorAs(whiteAnalogous.get(1).toInt());
|
||||
assertThat(0xffffffff).isSameColorAs(whiteAnalogous.get(2).toInt());
|
||||
assertThat(0xffffffff).isSameColorAs(whiteAnalogous.get(3).toInt());
|
||||
assertThat(0xffffffff).isSameColorAs(whiteAnalogous.get(4).toInt());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.systemui.monet.utils;
|
||||
|
||||
import static com.google.common.truth.Truth.assertAbout;
|
||||
|
||||
import com.google.common.truth.FailureMetadata;
|
||||
import com.google.common.truth.Subject;
|
||||
|
||||
/** Enables assertions involving colors as integers to log as hex codes instead of raw integers. */
|
||||
public final class ArgbSubject extends Subject {
|
||||
|
||||
public static ArgbSubject assertThat(int argb) {
|
||||
return assertAbout(ArgbSubject::new).that(argb);
|
||||
}
|
||||
|
||||
private final int actual;
|
||||
|
||||
private ArgbSubject(FailureMetadata failureMetadata, int subject) {
|
||||
super(failureMetadata, subject);
|
||||
this.actual = subject;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String actualCustomStringRepresentation() {
|
||||
return userFriendlyHexCode(actual);
|
||||
}
|
||||
|
||||
public void isSameColorAs(int otherArgb) {
|
||||
if (otherArgb != actual) {
|
||||
failWithActual("color", userFriendlyHexCode(otherArgb));
|
||||
}
|
||||
}
|
||||
|
||||
private static String userFriendlyHexCode(int integer) {
|
||||
String hexString = Integer.toHexString(integer);
|
||||
if (hexString.length() > 6) {
|
||||
return String.format("#%s", hexString.substring(hexString.length() - 6));
|
||||
} else if (hexString.length() == 6) {
|
||||
return String.format("#%s", hexString);
|
||||
} else {
|
||||
return String.format("??? #%s", hexString);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user