summaryrefslogtreecommitdiff
path: root/core/java
diff options
context:
space:
mode:
authorDanny Lin <danny@kdrag0n.dev>2021-10-05 16:02:15 -0700
committerSemavi Ulusoy <doc.divxm@gmail.com>2022-10-21 15:38:37 +0300
commitbaf93cb5a7471cb31c4ae98cb199f22112e05cd7 (patch)
tree5f94f7eccc952ec8f3b5249eb746638c6f7f5bd1 /core/java
parent05728b383b5608928581a351e608a58f9a495229 (diff)
graphics: Blend ARGB colors using Oklab
Blending colors directly in sRGB color space can result in unnatural color shifts, and while linear sRGB is generally an improvement, it can still suffer from the same issues. The advent of dynamic tinted UI themes makes accurate blending more important in animations that make use of blendARGB, such as the bubble notification expand/collapse transition, so use Oklab [1] for blending in order to improve quality. Oklab is one of the best perceptually-uniform color spaces currently available [2] that is also relatively simple and performant enough to use in this context. Because the last step of Oklab is a linear matrix transformation that is immediately reverted in the context of blending, the transformation is unnecessary and has been removed for performance; see [3] for details. [1] https://bottosson.github.io/posts/oklab/ [2] https://raphlinus.github.io/color/2021/01/18/oklab-critique.html [3] https://www.shadertoy.com/view/ttcyRS Change-Id: If72336a042943a49a067436e128f5e6a66acf8f0 Signed-off-by: Pranav <npv12@iitbbs.ac.in>
Diffstat (limited to 'core/java')
-rw-r--r--core/java/com/android/internal/graphics/ColorUtils.java80
1 files changed, 75 insertions, 5 deletions
diff --git a/core/java/com/android/internal/graphics/ColorUtils.java b/core/java/com/android/internal/graphics/ColorUtils.java
index dff9551c0c07..585aa221c834 100644
--- a/core/java/com/android/internal/graphics/ColorUtils.java
+++ b/core/java/com/android/internal/graphics/ColorUtils.java
@@ -605,8 +605,59 @@ public final class ColorUtils {
: (XYZ_KAPPA * component + 16) / 116;
}
+ private static float cube(float x) {
+ return x * x * x;
+ }
+
+ // Linear -> sRGB
+ private static float srgbTransfer(float x) {
+ if (x >= 0.0031308f) {
+ return 1.055f * (float) Math.pow(x, 1.0f / 2.4f) - 0.055f;
+ } else {
+ return 12.92f * x;
+ }
+ }
+
+ // sRGB -> linear
+ private static float srgbTransferInv(float x) {
+ if (x >= 0.04045f) {
+ return (float) Math.pow((x + 0.055f) / 1.055f, 2.4f);
+ } else {
+ return x / 12.92f;
+ }
+ }
+
+ private static float srgbRed(@ColorInt int color) {
+ return srgbTransferInv(((float) Color.red(color)) / 255.0f);
+ }
+
+ private static float srgbGreen(@ColorInt int color) {
+ return srgbTransferInv(((float) Color.green(color)) / 255.0f);
+ }
+
+ private static float srgbBlue(@ColorInt int color) {
+ return srgbTransferInv(((float) Color.blue(color)) / 255.0f);
+ }
+
+ private static int srgbTransferToInt(float c) {
+ return Math.round(srgbTransfer(c) * 255.0f);
+ }
+
+ private static float rgbToOklabLp(float r, float g, float b) {
+ return (float) Math.cbrt(0.4122214708f * r + 0.5363325363f * g + 0.0514459929f * b);
+ }
+
+ private static float rgbToOklabMp(float r, float g, float b) {
+ return (float) Math.cbrt(0.2119034982f * r + 0.6806995451f * g + 0.1073969566f * b);
+ }
+
+ private static float rgbToOklabSp(float r, float g, float b) {
+ return (float) Math.cbrt(0.0883024619f * r + 0.2817188376f * g + 0.6299787005f * b);
+ }
+
/**
* Blend between two ARGB colors using the given ratio.
+ * This uses Oklab internally in order to perform a perceptually-uniform blend.
*
* <p>A blend ratio of 0.0 will result in {@code color1}, 0.5 will give an even blend,
* 1.0 will result in {@code color2}.</p>
@@ -620,10 +671,29 @@ public final class ColorUtils {
@FloatRange(from = 0.0, to = 1.0) float ratio) {
final float inverseRatio = 1 - ratio;
float a = Color.alpha(color1) * inverseRatio + Color.alpha(color2) * ratio;
- float r = Color.red(color1) * inverseRatio + Color.red(color2) * ratio;
- float g = Color.green(color1) * inverseRatio + Color.green(color2) * ratio;
- float b = Color.blue(color1) * inverseRatio + Color.blue(color2) * ratio;
- return Color.argb((int) a, (int) r, (int) g, (int) b);
+
+ float r1 = srgbRed(color1);
+ float g1 = srgbGreen(color1);
+ float b1 = srgbBlue(color1);
+ float lp1 = rgbToOklabLp(r1, g1, b1);
+ float mp1 = rgbToOklabMp(r1, g1, b1);
+ float sp1 = rgbToOklabSp(r1, g1, b1);
+
+ float r2 = srgbRed(color2);
+ float g2 = srgbGreen(color2);
+ float b2 = srgbBlue(color2);
+ float lp2 = rgbToOklabLp(r2, g2, b2);
+ float mp2 = rgbToOklabMp(r2, g2, b2);
+ float sp2 = rgbToOklabSp(r2, g2, b2);
+
+ float l = cube(lp1 * inverseRatio + lp2 * ratio);
+ float m = cube(mp1 * inverseRatio + mp2 * ratio);
+ float s = cube(sp1 * inverseRatio + sp2 * ratio);
+ int r = srgbTransferToInt(+4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s);
+ int g = srgbTransferToInt(-1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s);
+ int b = srgbTransferToInt(-0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s);
+
+ return Color.argb((int) a, r, g, b);
}
/**
@@ -696,4 +766,4 @@ public final class ColorUtils {
double calculateContrast(int foreground, int background, int alpha);
}
-} \ No newline at end of file
+}