Loading libs/hwui/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -779,6 +779,7 @@ cc_test { "tests/unit/CommonPoolTests.cpp", "tests/unit/DamageAccumulatorTests.cpp", "tests/unit/DeferredLayerUpdaterTests.cpp", "tests/unit/DrawTextFunctorTest.cpp", "tests/unit/EglManagerTests.cpp", "tests/unit/FatVectorTests.cpp", "tests/unit/GraphicsStatsServiceTests.cpp", Loading libs/hwui/hwui/DrawTextFunctor.h +37 −17 Original line number Diff line number Diff line Loading @@ -76,6 +76,41 @@ static void simplifyPaint(int color, Paint* paint) { paint->setBlendMode(SkBlendMode::kSrcOver); } namespace { static bool shouldDarkenTextForHighContrast(const uirenderer::Lab& lab) { // LINT.IfChange(hct_darken) return lab.L <= 50; // LINT.ThenChange(/core/java/android/text/Layout.java:hct_darken) } } // namespace static void adjustHighContrastInnerTextColor(uirenderer::Lab* lab) { bool darken = shouldDarkenTextForHighContrast(*lab); bool isGrayscale = abs(lab->a) < 10 && abs(lab->b) < 10; if (isGrayscale) { // For near-grayscale text we first remove all color. lab->a = lab->b = 0; if (lab->L > 40 && lab->L < 60) { // Text near "middle gray" is pushed to a more contrasty gray. lab->L = darken ? 20 : 80; } else { // Other grayscale text is pushed completely white or black. lab->L = darken ? 0 : 100; } } else { // For color text we ensure the text is bright enough (for light text) // or dark enough (for dark text) to stand out against the background, // without touching the A and B components so we retain color. if (darken && lab->L > 20.f) { lab->L = 20.0f; } else if (!darken && lab->L < 90.f) { lab->L = 90.0f; } } } class DrawTextFunctor { public: /** Loading Loading @@ -114,10 +149,8 @@ public: if (CC_UNLIKELY(canvas->isHighContrastText() && paint.getAlpha() != 0)) { // high contrast draw path int color = paint.getColor(); // LINT.IfChange(hct_darken) uirenderer::Lab lab = uirenderer::sRGBToLab(color); bool darken = lab.L <= 50; // LINT.ThenChange(/core/java/android/text/Layout.java:hct_darken) bool darken = shouldDarkenTextForHighContrast(lab); // outline gDrawTextBlobMode = DrawTextBlobMode::HctOutline; Loading @@ -130,20 +163,7 @@ public: gDrawTextBlobMode = DrawTextBlobMode::HctInner; Paint innerPaint(paint); if (flags::high_contrast_text_inner_text_color()) { // Preserve some color information while still ensuring sufficient contrast. // Thus we increase the lightness to make the color stand out against a black // background, and vice-versa. For grayscale, we retain some gray to indicate // states like disabled or to distinguish links. bool isGrayscale = abs(lab.a) < 1 && abs(lab.b) < 1; if (isGrayscale) { if (darken) { lab.L = lab.L < 40 ? 0 : 20; } else { lab.L = lab.L > 60 ? 100 : 80; } } else { lab.L = darken ? 20 : 90; } adjustHighContrastInnerTextColor(&lab); simplifyPaint(uirenderer::LabToSRGB(lab, SK_AlphaOPAQUE), &innerPaint); } else { simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint); Loading libs/hwui/tests/unit/DrawTextFunctorTest.cpp 0 → 100644 +88 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <gtest/gtest.h> #include "hwui/DrawTextFunctor.h" using namespace android; namespace { void testHighContrastInnerTextColor(float originalL, float originalA, float originalB, float expectedL, float expectedA, float expectedB) { uirenderer::Lab color = {originalL, originalA, originalB}; adjustHighContrastInnerTextColor(&color); EXPECT_FLOAT_EQ(color.L, expectedL); EXPECT_FLOAT_EQ(color.a, expectedA); EXPECT_FLOAT_EQ(color.b, expectedB); } TEST(DrawTextFunctorTest, BlackUnaffected) { testHighContrastInnerTextColor(0, 0, 0, 0, 0, 0); } TEST(DrawTextFunctorTest, WhiteUnaffected) { testHighContrastInnerTextColor(100, 0, 0, 100, 0, 0); } TEST(DrawTextFunctorTest, DarkGrayPushedToWhite) { testHighContrastInnerTextColor(10, 0, 0, 0, 0, 0); testHighContrastInnerTextColor(20, 0, 0, 0, 0, 0); } TEST(DrawTextFunctorTest, LightGrayPushedToWhite) { testHighContrastInnerTextColor(80, 0, 0, 100, 0, 0); testHighContrastInnerTextColor(90, 0, 0, 100, 0, 0); } TEST(DrawTextFunctorTest, MiddleDarkGrayPushedToDarkGray) { testHighContrastInnerTextColor(41, 0, 0, 20, 0, 0); testHighContrastInnerTextColor(49, 0, 0, 20, 0, 0); } TEST(DrawTextFunctorTest, MiddleLightGrayPushedToLightGray) { testHighContrastInnerTextColor(51, 0, 0, 80, 0, 0); testHighContrastInnerTextColor(59, 0, 0, 80, 0, 0); } TEST(DrawTextFunctorTest, PaleColorTreatedAsGrayscaleAndPushedToWhite) { testHighContrastInnerTextColor(75, 5, -5, 100, 0, 0); testHighContrastInnerTextColor(85, -6, 8, 100, 0, 0); } TEST(DrawTextFunctorTest, PaleColorTreatedAsGrayscaleAndPushedToBlack) { testHighContrastInnerTextColor(25, 5, -5, 0, 0, 0); testHighContrastInnerTextColor(35, -6, 8, 0, 0, 0); } TEST(DrawTextFunctorTest, ColorfulColorIsLightened) { testHighContrastInnerTextColor(70, 100, -100, 90, 100, -100); } TEST(DrawTextFunctorTest, ColorfulLightColorIsUntouched) { testHighContrastInnerTextColor(95, 100, -100, 95, 100, -100); } TEST(DrawTextFunctorTest, ColorfulColorIsDarkened) { testHighContrastInnerTextColor(30, 100, -100, 20, 100, -100); } TEST(DrawTextFunctorTest, ColorfulDarkColorIsUntouched) { testHighContrastInnerTextColor(5, 100, -100, 5, 100, -100); } } // namespace Loading
libs/hwui/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -779,6 +779,7 @@ cc_test { "tests/unit/CommonPoolTests.cpp", "tests/unit/DamageAccumulatorTests.cpp", "tests/unit/DeferredLayerUpdaterTests.cpp", "tests/unit/DrawTextFunctorTest.cpp", "tests/unit/EglManagerTests.cpp", "tests/unit/FatVectorTests.cpp", "tests/unit/GraphicsStatsServiceTests.cpp", Loading
libs/hwui/hwui/DrawTextFunctor.h +37 −17 Original line number Diff line number Diff line Loading @@ -76,6 +76,41 @@ static void simplifyPaint(int color, Paint* paint) { paint->setBlendMode(SkBlendMode::kSrcOver); } namespace { static bool shouldDarkenTextForHighContrast(const uirenderer::Lab& lab) { // LINT.IfChange(hct_darken) return lab.L <= 50; // LINT.ThenChange(/core/java/android/text/Layout.java:hct_darken) } } // namespace static void adjustHighContrastInnerTextColor(uirenderer::Lab* lab) { bool darken = shouldDarkenTextForHighContrast(*lab); bool isGrayscale = abs(lab->a) < 10 && abs(lab->b) < 10; if (isGrayscale) { // For near-grayscale text we first remove all color. lab->a = lab->b = 0; if (lab->L > 40 && lab->L < 60) { // Text near "middle gray" is pushed to a more contrasty gray. lab->L = darken ? 20 : 80; } else { // Other grayscale text is pushed completely white or black. lab->L = darken ? 0 : 100; } } else { // For color text we ensure the text is bright enough (for light text) // or dark enough (for dark text) to stand out against the background, // without touching the A and B components so we retain color. if (darken && lab->L > 20.f) { lab->L = 20.0f; } else if (!darken && lab->L < 90.f) { lab->L = 90.0f; } } } class DrawTextFunctor { public: /** Loading Loading @@ -114,10 +149,8 @@ public: if (CC_UNLIKELY(canvas->isHighContrastText() && paint.getAlpha() != 0)) { // high contrast draw path int color = paint.getColor(); // LINT.IfChange(hct_darken) uirenderer::Lab lab = uirenderer::sRGBToLab(color); bool darken = lab.L <= 50; // LINT.ThenChange(/core/java/android/text/Layout.java:hct_darken) bool darken = shouldDarkenTextForHighContrast(lab); // outline gDrawTextBlobMode = DrawTextBlobMode::HctOutline; Loading @@ -130,20 +163,7 @@ public: gDrawTextBlobMode = DrawTextBlobMode::HctInner; Paint innerPaint(paint); if (flags::high_contrast_text_inner_text_color()) { // Preserve some color information while still ensuring sufficient contrast. // Thus we increase the lightness to make the color stand out against a black // background, and vice-versa. For grayscale, we retain some gray to indicate // states like disabled or to distinguish links. bool isGrayscale = abs(lab.a) < 1 && abs(lab.b) < 1; if (isGrayscale) { if (darken) { lab.L = lab.L < 40 ? 0 : 20; } else { lab.L = lab.L > 60 ? 100 : 80; } } else { lab.L = darken ? 20 : 90; } adjustHighContrastInnerTextColor(&lab); simplifyPaint(uirenderer::LabToSRGB(lab, SK_AlphaOPAQUE), &innerPaint); } else { simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint); Loading
libs/hwui/tests/unit/DrawTextFunctorTest.cpp 0 → 100644 +88 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <gtest/gtest.h> #include "hwui/DrawTextFunctor.h" using namespace android; namespace { void testHighContrastInnerTextColor(float originalL, float originalA, float originalB, float expectedL, float expectedA, float expectedB) { uirenderer::Lab color = {originalL, originalA, originalB}; adjustHighContrastInnerTextColor(&color); EXPECT_FLOAT_EQ(color.L, expectedL); EXPECT_FLOAT_EQ(color.a, expectedA); EXPECT_FLOAT_EQ(color.b, expectedB); } TEST(DrawTextFunctorTest, BlackUnaffected) { testHighContrastInnerTextColor(0, 0, 0, 0, 0, 0); } TEST(DrawTextFunctorTest, WhiteUnaffected) { testHighContrastInnerTextColor(100, 0, 0, 100, 0, 0); } TEST(DrawTextFunctorTest, DarkGrayPushedToWhite) { testHighContrastInnerTextColor(10, 0, 0, 0, 0, 0); testHighContrastInnerTextColor(20, 0, 0, 0, 0, 0); } TEST(DrawTextFunctorTest, LightGrayPushedToWhite) { testHighContrastInnerTextColor(80, 0, 0, 100, 0, 0); testHighContrastInnerTextColor(90, 0, 0, 100, 0, 0); } TEST(DrawTextFunctorTest, MiddleDarkGrayPushedToDarkGray) { testHighContrastInnerTextColor(41, 0, 0, 20, 0, 0); testHighContrastInnerTextColor(49, 0, 0, 20, 0, 0); } TEST(DrawTextFunctorTest, MiddleLightGrayPushedToLightGray) { testHighContrastInnerTextColor(51, 0, 0, 80, 0, 0); testHighContrastInnerTextColor(59, 0, 0, 80, 0, 0); } TEST(DrawTextFunctorTest, PaleColorTreatedAsGrayscaleAndPushedToWhite) { testHighContrastInnerTextColor(75, 5, -5, 100, 0, 0); testHighContrastInnerTextColor(85, -6, 8, 100, 0, 0); } TEST(DrawTextFunctorTest, PaleColorTreatedAsGrayscaleAndPushedToBlack) { testHighContrastInnerTextColor(25, 5, -5, 0, 0, 0); testHighContrastInnerTextColor(35, -6, 8, 0, 0, 0); } TEST(DrawTextFunctorTest, ColorfulColorIsLightened) { testHighContrastInnerTextColor(70, 100, -100, 90, 100, -100); } TEST(DrawTextFunctorTest, ColorfulLightColorIsUntouched) { testHighContrastInnerTextColor(95, 100, -100, 95, 100, -100); } TEST(DrawTextFunctorTest, ColorfulColorIsDarkened) { testHighContrastInnerTextColor(30, 100, -100, 20, 100, -100); } TEST(DrawTextFunctorTest, ColorfulDarkColorIsUntouched) { testHighContrastInnerTextColor(5, 100, -100, 5, 100, -100); } } // namespace