diff options
| author | Danny Lin <danny@kdrag0n.dev> | 2021-10-05 16:02:15 -0700 |
|---|---|---|
| committer | Semavi Ulusoy <doc.divxm@gmail.com> | 2022-10-21 15:38:37 +0300 |
| commit | baf93cb5a7471cb31c4ae98cb199f22112e05cd7 (patch) | |
| tree | 5f94f7eccc952ec8f3b5249eb746638c6f7f5bd1 /core/java | |
| parent | 05728b383b5608928581a351e608a58f9a495229 (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.java | 80 |
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 +} |
