LibGfx: Implement WCAG's contrast ratio in Color
We're going to implement the `contrast-color()` CSS function in the next commit, and the spec advises to use the contrast ratio definition as described in WCAG 2. So let's replace our `Color::contrast_ratio()` implementation by that. Co-authored-by: InvalidUsernameException <InvalidUsernameException@users.noreply.github.com>
This commit is contained in:
parent
d42b0f72ae
commit
49cdf582ff
6 changed files with 77 additions and 18 deletions
|
|
@ -67,6 +67,52 @@ Array<char, 4> format_to_8bit_compatible(u8 value)
|
|||
|
||||
}
|
||||
|
||||
// https://w3c.github.io/wcag/guidelines/22/#dfn-relative-luminance
|
||||
double Color::relative_luminance() const
|
||||
{
|
||||
// the relative brightness of any point in a colorspace, normalized to 0 for darkest black and 1 for lightest
|
||||
// white.
|
||||
|
||||
// Note 1:
|
||||
// For the sRGB colorspace, the relative luminance of a color is defined as L = 0.2126 * R + 0.7152 * G +
|
||||
// 0.0722 * B where R, G and B are defined as:
|
||||
// * if RsRGB <= 0.04045 then R = RsRGB/12.92 else R = ((RsRGB+0.055)/1.055) ^ 2.4
|
||||
// * if GsRGB <= 0.04045 then G = GsRGB/12.92 else G = ((GsRGB+0.055)/1.055) ^ 2.4
|
||||
// * if BsRGB <= 0.04045 then B = BsRGB/12.92 else B = ((BsRGB+0.055)/1.055) ^ 2.4
|
||||
// and RsRGB, GsRGB, and BsRGB are defined as:
|
||||
// * RsRGB = R8bit/255
|
||||
// * GsRGB = G8bit/255
|
||||
// * BsRGB = B8bit/255
|
||||
// The "^" character is the exponentiation operator. (Formula taken from [SRGB].)
|
||||
auto linearized_srgb_component = [](u8 component) {
|
||||
auto srgb_component = component / 255.0;
|
||||
if (srgb_component <= 0.04045)
|
||||
return srgb_component / 12.92;
|
||||
return AK::pow((srgb_component + 0.055) / 1.055, 2.4);
|
||||
};
|
||||
return 0.2126 * linearized_srgb_component(red())
|
||||
+ 0.7152 * linearized_srgb_component(green())
|
||||
+ 0.0722 * linearized_srgb_component(blue());
|
||||
}
|
||||
|
||||
// https://w3c.github.io/wcag/guidelines/22/#dfn-contrast-ratio
|
||||
double Color::contrast_ratio(Color const other) const
|
||||
{
|
||||
// (L1 + 0.05) / (L2 + 0.05), where
|
||||
// * L1 is the relative luminance of the lighter of the colors, and
|
||||
// * L2 is the relative luminance of the darker of the colors.
|
||||
auto l1 = relative_luminance();
|
||||
auto l2 = other.relative_luminance();
|
||||
auto darkest = min(l1, l2);
|
||||
auto brightest = max(l1, l2);
|
||||
return (brightest + 0.05) / (darkest + 0.05);
|
||||
}
|
||||
|
||||
Color Color::suggested_foreground_color() const
|
||||
{
|
||||
return contrast_ratio(Black) >= contrast_ratio(White) ? Black : White;
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-color-4/#serializing-sRGB-values
|
||||
void Color::serialize_a_srgb_value(StringBuilder& builder) const
|
||||
{
|
||||
|
|
|
|||
|
|
@ -324,19 +324,8 @@ public:
|
|||
return delta_alpha * delta_alpha / (2.0f * 255 * 255) + rgb_distance * alpha() * other.alpha() / (255 * 255);
|
||||
}
|
||||
|
||||
constexpr u8 luminosity() const
|
||||
{
|
||||
return round_to<u8>(red() * 0.2126f + green() * 0.7152f + blue() * 0.0722f);
|
||||
}
|
||||
|
||||
constexpr float contrast_ratio(Color other)
|
||||
{
|
||||
auto l1 = luminosity();
|
||||
auto l2 = other.luminosity();
|
||||
auto darkest = min(l1, l2) / 255.;
|
||||
auto brightest = max(l1, l2) / 255.;
|
||||
return (brightest + 0.05) / (darkest + 0.05);
|
||||
}
|
||||
double relative_luminance() const;
|
||||
double contrast_ratio(Color other) const;
|
||||
|
||||
constexpr Color sepia(float amount = 1.0f) const
|
||||
{
|
||||
|
|
@ -457,10 +446,7 @@ public:
|
|||
return Color(out_r, out_g, out_b);
|
||||
}
|
||||
|
||||
constexpr Color suggested_foreground_color() const
|
||||
{
|
||||
return luminosity() < 128 ? Color::White : Color::Black;
|
||||
}
|
||||
Color suggested_foreground_color() const;
|
||||
|
||||
private:
|
||||
constexpr explicit Color(BGRA8888 argb)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,34 @@
|
|||
#include <LibGfx/Color.h>
|
||||
#include <LibTest/TestCase.h>
|
||||
|
||||
TEST_CASE(relative_luminance)
|
||||
{
|
||||
auto const orange = Color(247, 143, 0);
|
||||
|
||||
EXPECT_APPROXIMATE(Color(Color::NamedColor::Black).relative_luminance(), 0.0);
|
||||
EXPECT_APPROXIMATE(Color(Color::NamedColor::White).relative_luminance(), 1.0);
|
||||
EXPECT_APPROXIMATE_WITH_ERROR(orange.relative_luminance(), 0.394190782, 0.0000005);
|
||||
}
|
||||
|
||||
TEST_CASE(contrast_ratio)
|
||||
{
|
||||
auto const orange = Color(247, 143, 0);
|
||||
|
||||
EXPECT_APPROXIMATE(Color(Color::NamedColor::Black).contrast_ratio(Color::NamedColor::White), 21.0);
|
||||
EXPECT_APPROXIMATE_WITH_ERROR(
|
||||
orange.contrast_ratio(Color::NamedColor::Black), 8.883815642, 0.0000005);
|
||||
EXPECT_APPROXIMATE_WITH_ERROR(
|
||||
orange.contrast_ratio(Color::NamedColor::White), 2.363849144, 0.0000005);
|
||||
EXPECT(orange.contrast_ratio(Color::NamedColor::Black) > orange.contrast_ratio(Color::NamedColor::White));
|
||||
}
|
||||
|
||||
TEST_CASE(suggested_foreground_color)
|
||||
{
|
||||
EXPECT_EQ(Color(Color::NamedColor::White), Color(Color::NamedColor::Black).suggested_foreground_color());
|
||||
EXPECT_EQ(Color(Color::NamedColor::Black), Color(Color::NamedColor::White).suggested_foreground_color());
|
||||
EXPECT_EQ(Color(Color::NamedColor::Black), Color(247, 143, 0).suggested_foreground_color());
|
||||
}
|
||||
|
||||
TEST_CASE(from_bgrx)
|
||||
{
|
||||
EXPECT_EQ(Color(0x00, 0x00, 0xff), Color::from_bgrx(0x000000ff));
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
|
@ -1,5 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<meta name="fuzzy" content="maxDifference=0-3;totalPixels=0-4660">
|
||||
<style>
|
||||
body>div>div {
|
||||
padding: 1em;
|
||||
|
|
|
|||
Loading…
Reference in a new issue