Add wallpaper color scheme
This commit is contained in:
parent
408a23563e
commit
8bb3737460
@ -201,4 +201,12 @@ val OpenSourceLicenses = arrayOf(
|
||||
licenseText = R.raw.license_apache_2,
|
||||
url = "https://github.com/promeG/TinyPinyin"
|
||||
),
|
||||
OpenSourceLibrary(
|
||||
name = "material-color-utilities",
|
||||
copyrightNote = "Copyright 2021 Google LLC",
|
||||
description = "Algorithms and utilities that power the Material Design 3 (M3) color system, including choosing theme colors from images and creating tones of colors; all in a new color space.",
|
||||
licenseName = R.string.apache_license_name,
|
||||
licenseText = R.raw.license_apache_2,
|
||||
url = "https://github.com/material-foundation/material-color-utilities"
|
||||
),
|
||||
)
|
||||
@ -8,7 +8,6 @@ buildscript {
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:7.1.3")
|
||||
classpath(libs.kotlin.gradle)
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10")
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
|
||||
@ -387,6 +387,7 @@
|
||||
<string name="preference_screen_colors">Color scheme</string>
|
||||
<string name="preference_colors_default">Default</string>
|
||||
<string name="preference_colors_bw">Black and White</string>
|
||||
<string name="preference_colors_wallpaper">From wallpaper</string>
|
||||
<string name="preference_screen_about">About</string>
|
||||
<string name="preference_version">Version</string>
|
||||
<string name="preference_category_links">Links</string>
|
||||
|
||||
1
material-color-utilities/.gitignore
vendored
Normal file
1
material-color-utilities/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
||||
42
material-color-utilities/build.gradle.kts
Normal file
42
material-color-utilities/build.gradle.kts
Normal file
@ -0,0 +1,42 @@
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = sdk.versions.compileSdk.get().toInt()
|
||||
|
||||
defaultConfig {
|
||||
minSdk = sdk.versions.minSdk.get().toInt()
|
||||
targetSdk = sdk.versions.targetSdk.get().toInt()
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.bundles.kotlin)
|
||||
implementation(libs.androidx.core)
|
||||
|
||||
implementation(libs.koin.android)
|
||||
|
||||
}
|
||||
0
material-color-utilities/consumer-rules.pro
Normal file
0
material-color-utilities/consumer-rules.pro
Normal file
21
material-color-utilities/proguard-rules.pro
vendored
Normal file
21
material-color-utilities/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
5
material-color-utilities/src/main/AndroidManifest.xml
Normal file
5
material-color-utilities/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="de.mm20.launcher2.materialcolorutilities">
|
||||
|
||||
</manifest>
|
||||
435
material-color-utilities/src/main/java/hct/Cam16.java
Normal file
435
material-color-utilities/src/main/java/hct/Cam16.java
Normal file
@ -0,0 +1,435 @@
|
||||
/*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* 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 hct;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
|
||||
import 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 float[][] XYZ_TO_CAM16RGB = {
|
||||
{0.401288f, 0.650173f, -0.051461f},
|
||||
{-0.250268f, 1.204414f, 0.045854f},
|
||||
{-0.002079f, 0.048952f, 0.953127f}
|
||||
};
|
||||
|
||||
// Transforms 'cone'/'RGB' responses in CAM16 to XYZ color space coordinates.
|
||||
static final float[][] CAM16RGB_TO_XYZ = {
|
||||
{1.8620678f, -1.0112547f, 0.14918678f},
|
||||
{0.38752654f, 0.62144744f, -0.00897398f},
|
||||
{-0.01584150f, -0.03412294f, 1.0499644f}
|
||||
};
|
||||
|
||||
// CAM16 color dimensions, see getters for documentation.
|
||||
private final float hue;
|
||||
private final float chroma;
|
||||
private final float j;
|
||||
private final float q;
|
||||
private final float m;
|
||||
private final float s;
|
||||
|
||||
// Coordinates in UCS space. Used to determine color distance, like delta E equations in L*a*b*.
|
||||
private final float jstar;
|
||||
private final float astar;
|
||||
private final float bstar;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
float distance(Cam16 other) {
|
||||
float dJ = getJStar() - other.getJStar();
|
||||
float dA = getAStar() - other.getAStar();
|
||||
float dB = getBStar() - other.getBStar();
|
||||
double dEPrime = Math.sqrt(dJ * dJ + dA * dA + dB * dB);
|
||||
double dE = 1.41 * Math.pow(dEPrime, 0.63);
|
||||
return (float) dE;
|
||||
}
|
||||
|
||||
/** Hue in CAM16 */
|
||||
public float getHue() {
|
||||
return hue;
|
||||
}
|
||||
|
||||
/** Chroma in CAM16 */
|
||||
public float getChroma() {
|
||||
return chroma;
|
||||
}
|
||||
|
||||
/** Lightness in CAM16 */
|
||||
public float 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 float 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 float 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 float getS() {
|
||||
return s;
|
||||
}
|
||||
|
||||
/** Lightness coordinate in CAM16-UCS */
|
||||
public float getJStar() {
|
||||
return jstar;
|
||||
}
|
||||
|
||||
/** a* coordinate in CAM16-UCS */
|
||||
public float getAStar() {
|
||||
return astar;
|
||||
}
|
||||
|
||||
/** b* coordinate in CAM16-UCS */
|
||||
public float 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(
|
||||
float hue,
|
||||
float chroma,
|
||||
float j,
|
||||
float q,
|
||||
float m,
|
||||
float s,
|
||||
float jstar,
|
||||
float astar,
|
||||
float 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);
|
||||
float redL = (float) ColorUtils.linearized(red);
|
||||
float greenL = (float) ColorUtils.linearized(green);
|
||||
float blueL = (float) ColorUtils.linearized(blue);
|
||||
float x = 0.41233895f * redL + 0.35762064f * greenL + 0.18051042f * blueL;
|
||||
float y = 0.2126f * redL + 0.7152f * greenL + 0.0722f * blueL;
|
||||
float z = 0.01932141f * redL + 0.11916382f * greenL + 0.95034478f * blueL;
|
||||
|
||||
// Transform XYZ to 'cone'/'rgb' responses
|
||||
float[][] matrix = XYZ_TO_CAM16RGB;
|
||||
float rT = (x * matrix[0][0]) + (y * matrix[0][1]) + (z * matrix[0][2]);
|
||||
float gT = (x * matrix[1][0]) + (y * matrix[1][1]) + (z * matrix[1][2]);
|
||||
float bT = (x * matrix[2][0]) + (y * matrix[2][1]) + (z * matrix[2][2]);
|
||||
|
||||
// Discount illuminant
|
||||
float rD = viewingConditions.getRgbD()[0] * rT;
|
||||
float gD = viewingConditions.getRgbD()[1] * gT;
|
||||
float bD = viewingConditions.getRgbD()[2] * bT;
|
||||
|
||||
// Chromatic adaptation
|
||||
float rAF = (float) Math.pow(viewingConditions.getFl() * Math.abs(rD) / 100.0, 0.42);
|
||||
float gAF = (float) Math.pow(viewingConditions.getFl() * Math.abs(gD) / 100.0, 0.42);
|
||||
float bAF = (float) Math.pow(viewingConditions.getFl() * Math.abs(bD) / 100.0, 0.42);
|
||||
float rA = Math.signum(rD) * 400.0f * rAF / (rAF + 27.13f);
|
||||
float gA = Math.signum(gD) * 400.0f * gAF / (gAF + 27.13f);
|
||||
float bA = Math.signum(bD) * 400.0f * bAF / (bAF + 27.13f);
|
||||
|
||||
// redness-greenness
|
||||
float a = (float) (11.0 * rA + -12.0 * gA + bA) / 11.0f;
|
||||
// yellowness-blueness
|
||||
float b = (float) (rA + gA - 2.0 * bA) / 9.0f;
|
||||
|
||||
// auxiliary components
|
||||
float u = (20.0f * rA + 20.0f * gA + 21.0f * bA) / 20.0f;
|
||||
float p2 = (40.0f * rA + 20.0f * gA + bA) / 20.0f;
|
||||
|
||||
// hue
|
||||
float atan2 = (float) Math.atan2(b, a);
|
||||
float atanDegrees = atan2 * 180.0f / (float) Math.PI;
|
||||
float hue =
|
||||
atanDegrees < 0
|
||||
? atanDegrees + 360.0f
|
||||
: atanDegrees >= 360 ? atanDegrees - 360.0f : atanDegrees;
|
||||
float hueRadians = hue * (float) Math.PI / 180.0f;
|
||||
|
||||
// achromatic response to color
|
||||
float ac = p2 * viewingConditions.getNbb();
|
||||
|
||||
// CAM16 lightness and brightness
|
||||
float j =
|
||||
100.0f
|
||||
* (float)
|
||||
Math.pow(
|
||||
ac / viewingConditions.getAw(),
|
||||
viewingConditions.getC() * viewingConditions.getZ());
|
||||
float q =
|
||||
4.0f
|
||||
/ viewingConditions.getC()
|
||||
* (float) Math.sqrt(j / 100.0f)
|
||||
* (viewingConditions.getAw() + 4.0f)
|
||||
* viewingConditions.getFlRoot();
|
||||
|
||||
// CAM16 chroma, colorfulness, and saturation.
|
||||
float huePrime = (hue < 20.14) ? hue + 360 : hue;
|
||||
float eHue = 0.25f * (float) (Math.cos(Math.toRadians(huePrime) + 2.0) + 3.8);
|
||||
float p1 = 50000.0f / 13.0f * eHue * viewingConditions.getNc() * viewingConditions.getNcb();
|
||||
float t = p1 * (float) Math.hypot(a, b) / (u + 0.305f);
|
||||
float alpha =
|
||||
(float) Math.pow(1.64 - Math.pow(0.29, viewingConditions.getN()), 0.73)
|
||||
* (float) Math.pow(t, 0.9);
|
||||
// CAM16 chroma, colorfulness, saturation
|
||||
float c = alpha * (float) Math.sqrt(j / 100.0);
|
||||
float m = c * viewingConditions.getFlRoot();
|
||||
float s =
|
||||
50.0f
|
||||
* (float)
|
||||
Math.sqrt((alpha * viewingConditions.getC()) / (viewingConditions.getAw() + 4.0f));
|
||||
|
||||
// CAM16-UCS components
|
||||
float jstar = (1.0f + 100.0f * 0.007f) * j / (1.0f + 0.007f * j);
|
||||
float mstar = 1.0f / 0.0228f * (float) Math.log1p(0.0228f * m);
|
||||
float astar = mstar * (float) Math.cos(hueRadians);
|
||||
float bstar = mstar * (float) 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(float j, float c, float 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(
|
||||
float j, float c, float h, ViewingConditions viewingConditions) {
|
||||
float q =
|
||||
4.0f
|
||||
/ viewingConditions.getC()
|
||||
* (float) Math.sqrt(j / 100.0)
|
||||
* (viewingConditions.getAw() + 4.0f)
|
||||
* viewingConditions.getFlRoot();
|
||||
float m = c * viewingConditions.getFlRoot();
|
||||
float alpha = c / (float) Math.sqrt(j / 100.0);
|
||||
float s =
|
||||
50.0f
|
||||
* (float)
|
||||
Math.sqrt((alpha * viewingConditions.getC()) / (viewingConditions.getAw() + 4.0f));
|
||||
|
||||
float hueRadians = h * (float) Math.PI / 180.0f;
|
||||
float jstar = (1.0f + 100.0f * 0.007f) * j / (1.0f + 0.007f * j);
|
||||
float mstar = 1.0f / 0.0228f * (float) Math.log1p(0.0228 * m);
|
||||
float astar = mstar * (float) Math.cos(hueRadians);
|
||||
float bstar = mstar * (float) 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(float jstar, float astar, float 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(
|
||||
float jstar, float astar, float bstar, ViewingConditions viewingConditions) {
|
||||
|
||||
double m = Math.hypot(astar, bstar);
|
||||
double m2 = Math.expm1(m * 0.0228f) / 0.0228f;
|
||||
double c = m2 / viewingConditions.getFlRoot();
|
||||
double h = Math.atan2(bstar, astar) * (180.0f / Math.PI);
|
||||
if (h < 0.0) {
|
||||
h += 360.0f;
|
||||
}
|
||||
float j = jstar / (1f - (jstar - 100f) * 0.007f);
|
||||
return fromJchInViewingConditions(j, (float) c, (float) 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 getInt() {
|
||||
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) {
|
||||
float alpha =
|
||||
(getChroma() == 0.0 || getJ() == 0.0)
|
||||
? 0.0f
|
||||
: getChroma() / (float) Math.sqrt(getJ() / 100.0);
|
||||
|
||||
float t =
|
||||
(float)
|
||||
Math.pow(
|
||||
alpha / Math.pow(1.64 - Math.pow(0.29, viewingConditions.getN()), 0.73), 1.0 / 0.9);
|
||||
float hRad = getHue() * (float) Math.PI / 180.0f;
|
||||
|
||||
float eHue = 0.25f * (float) (Math.cos(hRad + 2.0) + 3.8);
|
||||
float ac =
|
||||
viewingConditions.getAw()
|
||||
* (float)
|
||||
Math.pow(getJ() / 100.0, 1.0 / viewingConditions.getC() / viewingConditions.getZ());
|
||||
float p1 = eHue * (50000.0f / 13.0f) * viewingConditions.getNc() * viewingConditions.getNcb();
|
||||
float p2 = (ac / viewingConditions.getNbb());
|
||||
|
||||
float hSin = (float) Math.sin(hRad);
|
||||
float hCos = (float) Math.cos(hRad);
|
||||
|
||||
float gamma = 23.0f * (p2 + 0.305f) * t / (23.0f * p1 + 11.0f * t * hCos + 108.0f * t * hSin);
|
||||
float a = gamma * hCos;
|
||||
float b = gamma * hSin;
|
||||
float rA = (460.0f * p2 + 451.0f * a + 288.0f * b) / 1403.0f;
|
||||
float gA = (460.0f * p2 - 891.0f * a - 261.0f * b) / 1403.0f;
|
||||
float bA = (460.0f * p2 - 220.0f * a - 6300.0f * b) / 1403.0f;
|
||||
|
||||
float rCBase = (float) max(0, (27.13 * Math.abs(rA)) / (400.0 - Math.abs(rA)));
|
||||
float rC =
|
||||
Math.signum(rA)
|
||||
* (100.0f / viewingConditions.getFl())
|
||||
* (float) Math.pow(rCBase, 1.0 / 0.42);
|
||||
float gCBase = (float) max(0, (27.13 * Math.abs(gA)) / (400.0 - Math.abs(gA)));
|
||||
float gC =
|
||||
Math.signum(gA)
|
||||
* (100.0f / viewingConditions.getFl())
|
||||
* (float) Math.pow(gCBase, 1.0 / 0.42);
|
||||
float bCBase = (float) max(0, (27.13 * Math.abs(bA)) / (400.0 - Math.abs(bA)));
|
||||
float bC =
|
||||
Math.signum(bA)
|
||||
* (100.0f / viewingConditions.getFl())
|
||||
* (float) Math.pow(bCBase, 1.0 / 0.42);
|
||||
float rF = rC / viewingConditions.getRgbD()[0];
|
||||
float gF = gC / viewingConditions.getRgbD()[1];
|
||||
float bF = bC / viewingConditions.getRgbD()[2];
|
||||
|
||||
float[][] matrix = CAM16RGB_TO_XYZ;
|
||||
float x = (rF * matrix[0][0]) + (gF * matrix[0][1]) + (bF * matrix[0][2]);
|
||||
float y = (rF * matrix[1][0]) + (gF * matrix[1][1]) + (bF * matrix[1][2]);
|
||||
float z = (rF * matrix[2][0]) + (gF * matrix[2][1]) + (bF * matrix[2][2]);
|
||||
|
||||
return ColorUtils.argbFromXyz(x, y, z);
|
||||
}
|
||||
}
|
||||
262
material-color-utilities/src/main/java/hct/Hct.java
Normal file
262
material-color-utilities/src/main/java/hct/Hct.java
Normal file
@ -0,0 +1,262 @@
|
||||
/*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* 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 hct;
|
||||
|
||||
import utils.ColorUtils;
|
||||
import utils.MathUtils;
|
||||
|
||||
/**
|
||||
* 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 float hue;
|
||||
private float chroma;
|
||||
private float tone;
|
||||
|
||||
/**
|
||||
* 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(float hue, float chroma, float tone) {
|
||||
return new Hct(hue, chroma, tone);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
Cam16 cam = Cam16.fromInt(argb);
|
||||
return new Hct(cam.getHue(), cam.getChroma(), (float) ColorUtils.lstarFromArgb(argb));
|
||||
}
|
||||
|
||||
private Hct(float hue, float chroma, float tone) {
|
||||
setInternalState(gamutMap(hue, chroma, tone));
|
||||
}
|
||||
|
||||
public float getHue() {
|
||||
return hue;
|
||||
}
|
||||
|
||||
public float getChroma() {
|
||||
return chroma;
|
||||
}
|
||||
|
||||
public float getTone() {
|
||||
return tone;
|
||||
}
|
||||
|
||||
public int toInt() {
|
||||
return gamutMap(hue, chroma, tone);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(float newHue) {
|
||||
setInternalState(gamutMap((float) MathUtils.sanitizeDegreesDouble(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(float newChroma) {
|
||||
setInternalState(gamutMap(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(float newTone) {
|
||||
setInternalState(gamutMap(hue, chroma, newTone));
|
||||
}
|
||||
|
||||
private void setInternalState(int argb) {
|
||||
Cam16 cam = Cam16.fromInt(argb);
|
||||
float tone = (float) ColorUtils.lstarFromArgb(argb);
|
||||
hue = cam.getHue();
|
||||
chroma = cam.getChroma();
|
||||
this.tone = tone;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the delta between the floor & ceiling of a binary search for maximum chroma at a hue and
|
||||
* tone is less than this, the binary search terminates.
|
||||
*/
|
||||
private static final float CHROMA_SEARCH_ENDPOINT = 0.4f;
|
||||
|
||||
/** The maximum color distance, in CAM16-UCS, between a requested color and the color returned. */
|
||||
private static final float DE_MAX = 1.0f;
|
||||
|
||||
/** The maximum difference between the requested L* and the L* returned. */
|
||||
private static final float DL_MAX = 0.2f;
|
||||
|
||||
/**
|
||||
* The minimum color distance, in CAM16-UCS, between a requested color and an 'exact' match. This
|
||||
* allows the binary search during gamut mapping to terminate much earlier when the error is
|
||||
* infinitesimal.
|
||||
*/
|
||||
private static final float DE_MAX_ERROR = 0.000000001f;
|
||||
|
||||
/**
|
||||
* When the delta between the floor & ceiling of a binary search for J, lightness in CAM16, is
|
||||
* less than this, the binary search terminates.
|
||||
*/
|
||||
private static final float LIGHTNESS_SEARCH_ENDPOINT = 0.01f;
|
||||
|
||||
/**
|
||||
* @param hue a number, in degrees, representing ex. red, orange, yellow, etc. Ranges from 0 <=
|
||||
* hue < 360.
|
||||
* @param chroma Informally, colorfulness. Ranges from 0 to roughly 150. Like all perceptually
|
||||
* accurate color systems, chroma has a different maximum for any given hue and tone, so the
|
||||
* color returned may be lower than the requested chroma.
|
||||
* @param tone Lightness. Ranges from 0 to 100.
|
||||
* @return ARGB representation of a color in default viewing conditions
|
||||
*/
|
||||
private static int gamutMap(float hue, float chroma, float tone) {
|
||||
return gamutMapInViewingConditions(hue, chroma, tone, ViewingConditions.DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param hue CAM16 hue.
|
||||
* @param chroma CAM16 chroma.
|
||||
* @param tone L*a*b* lightness.
|
||||
* @param viewingConditions Information about the environment where the color was observed.
|
||||
*/
|
||||
static int gamutMapInViewingConditions(
|
||||
float hue, float chroma, float tone, ViewingConditions viewingConditions) {
|
||||
|
||||
if (chroma < 1.0 || Math.round(tone) <= 0.0 || Math.round(tone) >= 100.0) {
|
||||
return ColorUtils.argbFromLstar(tone);
|
||||
}
|
||||
|
||||
hue = (float) MathUtils.sanitizeDegreesDouble(hue);
|
||||
|
||||
float high = chroma;
|
||||
float mid = chroma;
|
||||
float low = 0.0f;
|
||||
boolean isFirstLoop = true;
|
||||
|
||||
Cam16 answer = null;
|
||||
while (Math.abs(low - high) >= CHROMA_SEARCH_ENDPOINT) {
|
||||
Cam16 possibleAnswer = findCamByJ(hue, mid, tone);
|
||||
|
||||
if (isFirstLoop) {
|
||||
if (possibleAnswer != null) {
|
||||
return possibleAnswer.viewed(viewingConditions);
|
||||
} else {
|
||||
isFirstLoop = false;
|
||||
mid = low + (high - low) / 2.0f;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (possibleAnswer == null) {
|
||||
high = mid;
|
||||
} else {
|
||||
answer = possibleAnswer;
|
||||
low = mid;
|
||||
}
|
||||
|
||||
mid = low + (high - low) / 2.0f;
|
||||
}
|
||||
|
||||
if (answer == null) {
|
||||
return ColorUtils.argbFromLstar(tone);
|
||||
}
|
||||
|
||||
return answer.viewed(viewingConditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param hue CAM16 hue
|
||||
* @param chroma CAM16 chroma
|
||||
* @param tone L*a*b* lightness
|
||||
* @return CAM16 instance within error tolerance of the provided dimensions, or null.
|
||||
*/
|
||||
private static Cam16 findCamByJ(float hue, float chroma, float tone) {
|
||||
float low = 0.0f;
|
||||
float high = 100.0f;
|
||||
float mid = 0.0f;
|
||||
float bestdL = 1000.0f;
|
||||
float bestdE = 1000.0f;
|
||||
|
||||
Cam16 bestCam = null;
|
||||
while (Math.abs(low - high) > LIGHTNESS_SEARCH_ENDPOINT) {
|
||||
mid = low + (high - low) / 2;
|
||||
Cam16 camBeforeClip = Cam16.fromJch(mid, chroma, hue);
|
||||
int clipped = camBeforeClip.getInt();
|
||||
float clippedLstar = (float) ColorUtils.lstarFromArgb(clipped);
|
||||
float dL = Math.abs(tone - clippedLstar);
|
||||
|
||||
if (dL < DL_MAX) {
|
||||
Cam16 camClipped = Cam16.fromInt(clipped);
|
||||
float dE =
|
||||
camClipped.distance(Cam16.fromJch(camClipped.getJ(), camClipped.getChroma(), hue));
|
||||
if (dE <= DE_MAX && dE <= bestdE) {
|
||||
bestdL = dL;
|
||||
bestdE = dE;
|
||||
bestCam = camClipped;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestdL == 0 && bestdE < DE_MAX_ERROR) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (clippedLstar < tone) {
|
||||
low = mid;
|
||||
} else {
|
||||
high = mid;
|
||||
}
|
||||
}
|
||||
|
||||
return bestCam;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* 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 hct;
|
||||
|
||||
import utils.ColorUtils;
|
||||
import 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.make(
|
||||
new float[] {
|
||||
(float) ColorUtils.whitePointD65()[0],
|
||||
(float) ColorUtils.whitePointD65()[1],
|
||||
(float) ColorUtils.whitePointD65()[2]
|
||||
},
|
||||
(float) (200.0f / Math.PI * ColorUtils.yFromLstar(50.0f) / 100.f),
|
||||
50.0f,
|
||||
2.0f,
|
||||
false);
|
||||
|
||||
private final float aw;
|
||||
private final float nbb;
|
||||
private final float ncb;
|
||||
private final float c;
|
||||
private final float nc;
|
||||
private final float n;
|
||||
private final float[] rgbD;
|
||||
private final float fl;
|
||||
private final float flRoot;
|
||||
private final float z;
|
||||
|
||||
public float getAw() {
|
||||
return aw;
|
||||
}
|
||||
|
||||
public float getN() {
|
||||
return n;
|
||||
}
|
||||
|
||||
public float getNbb() {
|
||||
return nbb;
|
||||
}
|
||||
|
||||
float getNcb() {
|
||||
return ncb;
|
||||
}
|
||||
|
||||
float getC() {
|
||||
return c;
|
||||
}
|
||||
|
||||
float getNc() {
|
||||
return nc;
|
||||
}
|
||||
|
||||
public float[] getRgbD() {
|
||||
return rgbD;
|
||||
}
|
||||
|
||||
float getFl() {
|
||||
return fl;
|
||||
}
|
||||
|
||||
public float getFlRoot() {
|
||||
return flRoot;
|
||||
}
|
||||
|
||||
float 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.
|
||||
*/
|
||||
static ViewingConditions make(
|
||||
float[] whitePoint,
|
||||
float adaptingLuminance,
|
||||
float backgroundLstar,
|
||||
float surround,
|
||||
boolean discountingIlluminant) {
|
||||
// Transform white point XYZ to 'cone'/'rgb' responses
|
||||
float[][] matrix = Cam16.XYZ_TO_CAM16RGB;
|
||||
float[] xyz = whitePoint;
|
||||
float rW = (xyz[0] * matrix[0][0]) + (xyz[1] * matrix[0][1]) + (xyz[2] * matrix[0][2]);
|
||||
float gW = (xyz[0] * matrix[1][0]) + (xyz[1] * matrix[1][1]) + (xyz[2] * matrix[1][2]);
|
||||
float bW = (xyz[0] * matrix[2][0]) + (xyz[1] * matrix[2][1]) + (xyz[2] * matrix[2][2]);
|
||||
float f = 0.8f + (surround / 10.0f);
|
||||
float c =
|
||||
(f >= 0.9)
|
||||
? (float) MathUtils.lerp(0.59f, 0.69f, ((f - 0.9f) * 10.0f))
|
||||
: (float) MathUtils.lerp(0.525f, 0.59f, ((f - 0.8f) * 10.0f));
|
||||
float d =
|
||||
discountingIlluminant
|
||||
? 1.0f
|
||||
: f * (1.0f - ((1.0f / 3.6f) * (float) Math.exp((-adaptingLuminance - 42.0f) / 92.0f)));
|
||||
d = (d > 1.0) ? 1.0f : (d < 0.0) ? 0.0f : d;
|
||||
float nc = f;
|
||||
float[] rgbD =
|
||||
new float[] {
|
||||
d * (100.0f / rW) + 1.0f - d, d * (100.0f / gW) + 1.0f - d, d * (100.0f / bW) + 1.0f - d
|
||||
};
|
||||
float k = 1.0f / (5.0f * adaptingLuminance + 1.0f);
|
||||
float k4 = k * k * k * k;
|
||||
float k4F = 1.0f - k4;
|
||||
float fl =
|
||||
(k4 * adaptingLuminance) + (0.1f * k4F * k4F * (float) Math.cbrt(5.0 * adaptingLuminance));
|
||||
float n = (float) (ColorUtils.yFromLstar(backgroundLstar) / whitePoint[1]);
|
||||
float z = 1.48f + (float) Math.sqrt(n);
|
||||
float nbb = 0.725f / (float) Math.pow(n, 0.2);
|
||||
float ncb = nbb;
|
||||
float[] rgbAFactors =
|
||||
new float[] {
|
||||
(float) Math.pow(fl * rgbD[0] * rW / 100.0, 0.42),
|
||||
(float) Math.pow(fl * rgbD[1] * gW / 100.0, 0.42),
|
||||
(float) Math.pow(fl * rgbD[2] * bW / 100.0, 0.42)
|
||||
};
|
||||
|
||||
float[] rgbA =
|
||||
new float[] {
|
||||
(400.0f * rgbAFactors[0]) / (rgbAFactors[0] + 27.13f),
|
||||
(400.0f * rgbAFactors[1]) / (rgbAFactors[1] + 27.13f),
|
||||
(400.0f * rgbAFactors[2]) / (rgbAFactors[2] + 27.13f)
|
||||
};
|
||||
|
||||
float aw = ((2.0f * rgbA[0]) + rgbA[1] + (0.05f * rgbA[2])) * nbb;
|
||||
return new ViewingConditions(n, aw, nbb, ncb, c, nc, rgbD, fl, (float) Math.pow(fl, 0.25), z);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(
|
||||
float n,
|
||||
float aw,
|
||||
float nbb,
|
||||
float ncb,
|
||||
float c,
|
||||
float nc,
|
||||
float[] rgbD,
|
||||
float fl,
|
||||
float flRoot,
|
||||
float 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,54 @@
|
||||
/*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* 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 palettes;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
|
||||
import 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);
|
||||
}
|
||||
|
||||
private CorePalette(int argb) {
|
||||
Hct hct = Hct.fromInt(argb);
|
||||
float hue = hct.getHue();
|
||||
this.a1 = TonalPalette.fromHueAndChroma(hue, max(48f, hct.getChroma()));
|
||||
this.a2 = TonalPalette.fromHueAndChroma(hue, 16f);
|
||||
this.a3 = TonalPalette.fromHueAndChroma(hue + 60f, 24f);
|
||||
this.n1 = TonalPalette.fromHueAndChroma(hue, 4f);
|
||||
this.n2 = TonalPalette.fromHueAndChroma(hue, 8f);
|
||||
this.error = TonalPalette.fromHueAndChroma(25, 84f);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* 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 palettes;
|
||||
|
||||
import 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;
|
||||
float hue;
|
||||
float 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 final TonalPalette fromInt(int argb) {
|
||||
Hct hct = Hct.fromInt(argb);
|
||||
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 final TonalPalette fromHueAndChroma(float hue, float chroma) {
|
||||
return new TonalPalette(hue, chroma);
|
||||
}
|
||||
|
||||
private TonalPalette(float hue, float 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;
|
||||
}
|
||||
}
|
||||
717
material-color-utilities/src/main/java/scheme/Scheme.java
Normal file
717
material-color-utilities/src/main/java/scheme/Scheme.java
Normal file
@ -0,0 +1,717 @@
|
||||
/*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* 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 scheme;
|
||||
|
||||
import 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 shadow;
|
||||
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 shadow,
|
||||
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.shadow = shadow;
|
||||
this.inverseSurface = inverseSurface;
|
||||
this.inverseOnSurface = inverseOnSurface;
|
||||
this.inversePrimary = inversePrimary;
|
||||
}
|
||||
|
||||
public static Scheme light(int argb) {
|
||||
CorePalette core = CorePalette.of(argb);
|
||||
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))
|
||||
.withShadow(core.n1.tone(0))
|
||||
.withInverseSurface(core.n1.tone(20))
|
||||
.withInverseOnSurface(core.n1.tone(95))
|
||||
.withInversePrimary(core.a1.tone(80));
|
||||
}
|
||||
|
||||
public static Scheme dark(int argb) {
|
||||
CorePalette core = CorePalette.of(argb);
|
||||
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))
|
||||
.withShadow(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 getShadow() {
|
||||
return shadow;
|
||||
}
|
||||
|
||||
public void setShadow(int shadow) {
|
||||
this.shadow = shadow;
|
||||
}
|
||||
|
||||
public Scheme withShadow(int shadow) {
|
||||
this.shadow = shadow;
|
||||
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
|
||||
+ ", shadow="
|
||||
+ shadow
|
||||
+ ", 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 (shadow != scheme.shadow) {
|
||||
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 + shadow;
|
||||
result = 31 * result + inverseSurface;
|
||||
result = 31 * result + inverseOnSurface;
|
||||
result = 31 * result + inversePrimary;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
265
material-color-utilities/src/main/java/utils/ColorUtils.java
Normal file
265
material-color-utilities/src/main/java/utils/ColorUtils.java
Normal file
@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// This file is automatically generated. Do not modify it.
|
||||
|
||||
package 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);
|
||||
}
|
||||
|
||||
/** 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 fy = (lstar + 16.0) / 116.0;
|
||||
double fz = fy;
|
||||
double fx = fy;
|
||||
double kappa = 24389.0 / 27.0;
|
||||
double epsilon = 216.0 / 24389.0;
|
||||
boolean lExceedsEpsilonKappa = lstar > 8.0;
|
||||
double y = lExceedsEpsilonKappa ? fy * fy * fy : lstar / kappa;
|
||||
boolean cubeExceedEpsilon = fy * fy * fy > epsilon;
|
||||
double x = cubeExceedEpsilon ? fx * fx * fx : lstar / kappa;
|
||||
double z = cubeExceedEpsilon ? fz * fz * fz : lstar / kappa;
|
||||
double[] whitePoint = WHITE_POINT_D65;
|
||||
return argbFromXyz(x * whitePoint[0], y * whitePoint[1], z * whitePoint[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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] / 100.0;
|
||||
double e = 216.0 / 24389.0;
|
||||
if (y <= e) {
|
||||
return 24389.0 / 27.0 * y;
|
||||
} else {
|
||||
double yIntermediate = Math.pow(y, 1.0 / 3.0);
|
||||
return 116.0 * yIntermediate - 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) {
|
||||
double ke = 8.0;
|
||||
if (lstar > ke) {
|
||||
return Math.pow((lstar + 16.0) / 116.0, 3.0) * 100.0;
|
||||
} else {
|
||||
return lstar / 24389.0 / 27.0 * 100.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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
119
material-color-utilities/src/main/java/utils/MathUtils.java
Normal file
119
material-color-utilities/src/main/java/utils/MathUtils.java
Normal file
@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// This file is automatically generated. Do not modify it.
|
||||
|
||||
package 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;
|
||||
}
|
||||
|
||||
/** 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,34 @@
|
||||
/*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* 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 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);
|
||||
}
|
||||
}
|
||||
@ -15,6 +15,7 @@ message Settings {
|
||||
enum ColorScheme {
|
||||
Default = 0;
|
||||
BlackAndWhite = 1;
|
||||
Wallpaper = 2;
|
||||
}
|
||||
ColorScheme color_scheme = 6;
|
||||
bool dim_wallpaper = 7;
|
||||
|
||||
@ -385,3 +385,4 @@ dependencyResolutionManagement {
|
||||
include(":notifications")
|
||||
include(":accounts")
|
||||
include(":appshortcuts")
|
||||
include(":material-color-utilities")
|
||||
|
||||
@ -104,6 +104,8 @@ dependencies {
|
||||
implementation(libs.coil.core)
|
||||
implementation(libs.coil.compose)
|
||||
|
||||
implementation(project(":material-color-utilities"))
|
||||
|
||||
implementation(project(":base"))
|
||||
implementation(project(":i18n"))
|
||||
implementation(project(":compat"))
|
||||
|
||||
@ -71,6 +71,7 @@ fun AppearanceSettingsScreen() {
|
||||
summary = when (colorScheme) {
|
||||
ColorScheme.Default -> stringResource(R.string.preference_colors_default)
|
||||
ColorScheme.BlackAndWhite -> stringResource(R.string.preference_colors_bw)
|
||||
ColorScheme.Wallpaper -> stringResource(R.string.preference_colors_wallpaper)
|
||||
else -> null
|
||||
},
|
||||
onClick = {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package de.mm20.launcher2.ui.settings.colorscheme
|
||||
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.*
|
||||
@ -7,6 +8,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.RadioButtonChecked
|
||||
import androidx.compose.material.icons.rounded.RadioButtonUnchecked
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@ -17,44 +19,42 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
||||
import de.mm20.launcher2.preferences.Settings.AppearanceSettings
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.preferences.Preference
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||
import de.mm20.launcher2.ui.theme.getColorScheme
|
||||
import de.mm20.launcher2.ui.theme.colorSchemeAsState
|
||||
|
||||
@Composable
|
||||
fun ColorSchemeSettingsScreen() {
|
||||
val viewModel: ColorSchemeSettingsScreenVM = viewModel()
|
||||
val context = LocalContext.current
|
||||
|
||||
PreferenceScreen(title = stringResource(R.string.preference_screen_colors)) {
|
||||
item {
|
||||
PreferenceCategory {
|
||||
val theme by viewModel.theme.observeAsState()
|
||||
val darkTheme =
|
||||
theme == AppearanceSettings.Theme.Dark || theme == AppearanceSettings.Theme.System && isSystemInDarkTheme()
|
||||
val colorScheme by viewModel.colorScheme.observeAsState()
|
||||
|
||||
val items = listOf(
|
||||
val items = mutableListOf(
|
||||
AppearanceSettings.ColorScheme.Default to R.string.preference_colors_default,
|
||||
AppearanceSettings.ColorScheme.BlackAndWhite to R.string.preference_colors_bw
|
||||
AppearanceSettings.ColorScheme.BlackAndWhite to R.string.preference_colors_bw,
|
||||
)
|
||||
|
||||
if (isAtLeastApiLevel(Build.VERSION_CODES.O_MR1)) {
|
||||
items.add(
|
||||
AppearanceSettings.ColorScheme.Wallpaper to R.string.preference_colors_wallpaper
|
||||
)
|
||||
}
|
||||
|
||||
for (cs in items) {
|
||||
val scheme by colorSchemeAsState(cs.first)
|
||||
Preference(
|
||||
title = stringResource(cs.second),
|
||||
icon = if (colorScheme == cs.first) Icons.Rounded.RadioButtonChecked else Icons.Rounded.RadioButtonUnchecked,
|
||||
onClick = { viewModel.setColorScheme(cs.first) },
|
||||
controls = {
|
||||
ColorSchemePreview(
|
||||
getColorScheme(
|
||||
LocalContext.current,
|
||||
cs.first,
|
||||
darkTheme,
|
||||
)
|
||||
)
|
||||
ColorSchemePreview(scheme)
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -65,51 +65,54 @@ fun ColorSchemeSettingsScreen() {
|
||||
|
||||
@Composable
|
||||
fun ColorSchemePreview(colorScheme: ColorScheme) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(vertical = 12.dp)
|
||||
.width(72.dp)
|
||||
.height(36.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
MaterialTheme(colorScheme = colorScheme) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(vertical = 12.dp)
|
||||
.width(72.dp)
|
||||
.height(36.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Surface(
|
||||
tonalElevation = 1.dp,
|
||||
color = colorScheme.surface,
|
||||
modifier = Modifier
|
||||
.size(36.dp)
|
||||
) {}
|
||||
Surface(
|
||||
tonalElevation = 1.dp,
|
||||
color = colorScheme.surfaceVariant,
|
||||
modifier = Modifier
|
||||
.size(36.dp)
|
||||
) {}
|
||||
}
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Surface(
|
||||
tonalElevation = 1.dp,
|
||||
color = colorScheme.primary,
|
||||
modifier = Modifier
|
||||
.size(16.dp)
|
||||
) {}
|
||||
Surface(
|
||||
tonalElevation = 1.dp,
|
||||
color = colorScheme.secondary,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 8.dp)
|
||||
.size(16.dp)
|
||||
) {}
|
||||
Surface(
|
||||
tonalElevation = 1.dp,
|
||||
color = colorScheme.tertiary,
|
||||
modifier = Modifier
|
||||
.size(16.dp)
|
||||
) {}
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Surface(
|
||||
tonalElevation = 1.dp,
|
||||
color = MaterialTheme.colorScheme.surface,
|
||||
modifier = Modifier
|
||||
.size(36.dp)
|
||||
) {}
|
||||
Surface(
|
||||
tonalElevation = 1.dp,
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
modifier = Modifier
|
||||
.size(36.dp)
|
||||
) {}
|
||||
}
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Surface(
|
||||
tonalElevation = 1.dp,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier
|
||||
.size(16.dp)
|
||||
) {}
|
||||
Surface(
|
||||
tonalElevation = 1.dp,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 8.dp)
|
||||
.size(16.dp)
|
||||
) {}
|
||||
Surface(
|
||||
tonalElevation = 1.dp,
|
||||
color = MaterialTheme.colorScheme.tertiary,
|
||||
modifier = Modifier
|
||||
.size(16.dp)
|
||||
) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,24 +1,18 @@
|
||||
package de.mm20.launcher2.ui.theme
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.preferences.Settings.AppearanceSettings
|
||||
import de.mm20.launcher2.preferences.Settings.AppearanceSettings.Theme
|
||||
import de.mm20.launcher2.ui.theme.colorscheme.DarkBlackAndWhiteColorScheme
|
||||
import de.mm20.launcher2.ui.theme.colorscheme.DarkPre31DefaultColorScheme
|
||||
import de.mm20.launcher2.ui.theme.colorscheme.LightBlackAndWhiteColorScheme
|
||||
import de.mm20.launcher2.ui.theme.colorscheme.LightPre31DefaultColorScheme
|
||||
import de.mm20.launcher2.ui.theme.colorscheme.*
|
||||
import de.mm20.launcher2.ui.theme.typography.DefaultTypography
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.koin.androidx.compose.inject
|
||||
@ -34,34 +28,52 @@ fun LauncherTheme(
|
||||
val colorSchemePreference by remember { dataStore.data.map { it.appearance.colorScheme } }.collectAsState(
|
||||
AppearanceSettings.ColorScheme.Default
|
||||
)
|
||||
val themePreference by remember { dataStore.data.map { it.appearance.theme } }.collectAsState(
|
||||
Theme.System
|
||||
)
|
||||
|
||||
val darkTheme =
|
||||
themePreference == Theme.Dark || themePreference == Theme.System && isSystemInDarkTheme()
|
||||
val colorScheme by colorSchemeAsState(colorSchemePreference)
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = getColorScheme(LocalContext.current, colorSchemePreference, darkTheme),
|
||||
colorScheme = colorScheme,
|
||||
typography = DefaultTypography,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
fun getColorScheme(context: Context, colorScheme: AppearanceSettings.ColorScheme, darkTheme: Boolean): ColorScheme {
|
||||
return when (colorScheme) {
|
||||
AppearanceSettings.ColorScheme.BlackAndWhite -> {
|
||||
if (darkTheme) DarkBlackAndWhiteColorScheme
|
||||
else LightBlackAndWhiteColorScheme
|
||||
}
|
||||
else -> {
|
||||
if (darkTheme) {
|
||||
if (isAtLeastApiLevel(31)) dynamicDarkColorScheme(context)
|
||||
else DarkPre31DefaultColorScheme
|
||||
} else {
|
||||
if (isAtLeastApiLevel(31)) dynamicLightColorScheme(context)
|
||||
else LightPre31DefaultColorScheme
|
||||
@Composable
|
||||
fun colorSchemeAsState(colorScheme: AppearanceSettings.ColorScheme): MutableState<ColorScheme> {
|
||||
val context = LocalContext.current
|
||||
val dataStore: LauncherDataStore by inject()
|
||||
|
||||
val themePreference by remember { dataStore.data.map { it.appearance.theme } }.collectAsState(
|
||||
Theme.System
|
||||
)
|
||||
val darkTheme =
|
||||
themePreference == Theme.Dark || themePreference == Theme.System && isSystemInDarkTheme()
|
||||
|
||||
val state = remember(colorScheme, darkTheme) {
|
||||
mutableStateOf(
|
||||
when (colorScheme) {
|
||||
AppearanceSettings.ColorScheme.BlackAndWhite -> {
|
||||
if (darkTheme) DarkBlackAndWhiteColorScheme else LightBlackAndWhiteColorScheme
|
||||
}
|
||||
else -> {
|
||||
if (darkTheme) {
|
||||
if (isAtLeastApiLevel(31)) dynamicDarkColorScheme(context)
|
||||
else DarkPre31DefaultColorScheme
|
||||
} else {
|
||||
if (isAtLeastApiLevel(31)) dynamicLightColorScheme(context)
|
||||
else LightPre31DefaultColorScheme
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (colorScheme == AppearanceSettings.ColorScheme.Wallpaper && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
val wallpaperColors by wallpaperColorsAsState()
|
||||
LaunchedEffect(wallpaperColors, darkTheme) {
|
||||
state.value = WallpaperColorScheme(wallpaperColors, darkTheme)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import android.app.WallpaperManager
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
@ -0,0 +1,69 @@
|
||||
package de.mm20.launcher2.ui.theme.colorscheme
|
||||
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import de.mm20.launcher2.ui.theme.WallpaperColors
|
||||
import palettes.TonalPalette
|
||||
|
||||
fun WallpaperColorScheme(wallpaperColors: WallpaperColors, darkTheme: Boolean): ColorScheme {
|
||||
val primary = TonalPalette.fromInt(wallpaperColors.primary.toArgb())
|
||||
val secondary = TonalPalette.fromInt((wallpaperColors.secondary ?: wallpaperColors.primary).toArgb())
|
||||
val tertiary = TonalPalette.fromInt((wallpaperColors.tertiary ?: wallpaperColors.primary).toArgb())
|
||||
|
||||
val neutral1 = TonalPalette.fromInt(Color.Black.toArgb())
|
||||
val neutral2 = TonalPalette.fromInt(Color.Black.toArgb())
|
||||
|
||||
return if(darkTheme) {
|
||||
darkColorScheme(
|
||||
primary = Color(primary.tone(80)),
|
||||
onPrimary = Color(primary.tone(20)),
|
||||
primaryContainer = Color(primary.tone(30)),
|
||||
onPrimaryContainer = Color(primary.tone(90)),
|
||||
secondary = Color(secondary.tone(80)),
|
||||
onSecondary = Color(secondary.tone(20)),
|
||||
secondaryContainer = Color(secondary.tone(30)),
|
||||
onSecondaryContainer = Color(secondary.tone(90)),
|
||||
tertiary = Color(tertiary.tone(80)),
|
||||
onTertiary = Color(tertiary.tone(20)),
|
||||
tertiaryContainer = Color(tertiary.tone(30)),
|
||||
onTertiaryContainer = Color(tertiary.tone(90)),
|
||||
background = Color(neutral1.tone(10)),
|
||||
onBackground = Color(neutral1.tone(90)),
|
||||
surface = Color(neutral1.tone(10)),
|
||||
onSurface = Color(neutral1.tone(90)),
|
||||
surfaceVariant = Color(neutral2.tone(30)),
|
||||
onSurfaceVariant = Color(neutral2.tone(80)),
|
||||
outline = Color(neutral2.tone(60)),
|
||||
inverseSurface = Color(neutral1.tone(90)),
|
||||
inverseOnSurface = Color(neutral1.tone(20)),
|
||||
inversePrimary = Color(primary.tone(40)),
|
||||
)
|
||||
} else {
|
||||
lightColorScheme(
|
||||
primary = Color(primary.tone(40)),
|
||||
onPrimary = Color(primary.tone(100)),
|
||||
primaryContainer = Color(primary.tone(90)),
|
||||
onPrimaryContainer = Color(primary.tone(10)),
|
||||
secondary = Color(secondary.tone(40)),
|
||||
onSecondary = Color(secondary.tone(100)),
|
||||
secondaryContainer = Color(secondary.tone(90)),
|
||||
onSecondaryContainer = Color(secondary.tone(10)),
|
||||
tertiary = Color(tertiary.tone(40)),
|
||||
onTertiary = Color(tertiary.tone(100)),
|
||||
tertiaryContainer = Color(tertiary.tone(90)),
|
||||
onTertiaryContainer = Color(tertiary.tone(10)),
|
||||
background = Color(neutral1.tone(99)),
|
||||
onBackground = Color(neutral1.tone(10)),
|
||||
surface = Color(neutral1.tone(99)),
|
||||
onSurface = Color(neutral1.tone(10)),
|
||||
surfaceVariant = Color(neutral2.tone(90)),
|
||||
onSurfaceVariant = Color(neutral2.tone(30)),
|
||||
outline = Color(neutral2.tone(50)),
|
||||
inverseSurface = Color(neutral1.tone(20)),
|
||||
inverseOnSurface = Color(neutral1.tone(95)),
|
||||
inversePrimary = Color(primary.tone(80)),)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user