aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFerdinand Thiessen <opensource@fthiessen.de>2023-12-14 17:53:18 +0100
committerFerdinand Thiessen <opensource@fthiessen.de>2023-12-15 21:09:14 +0100
commit141d1e90260ca63f415d65060752f4c9d3e8eb28 (patch)
treedf37f6769f5fb67fec417ba7aea95e153188e8f4
parent2a205511c7002b7980a7c6db980f002013c7be7b (diff)
downloadnextcloud-server-141d1e90260ca63f415d65060752f4c9d3e8eb28.tar.gz
nextcloud-server-141d1e90260ca63f415d65060752f4c9d3e8eb28.zip
enh(theming): Adjust color utils to work as specified by WCAG (color contrast and luma calculation)
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
-rw-r--r--apps/theming/lib/Themes/CommonThemeTrait.php2
-rw-r--r--apps/theming/lib/Util.php55
-rw-r--r--apps/theming/tests/UtilTest.php29
3 files changed, 76 insertions, 10 deletions
diff --git a/apps/theming/lib/Themes/CommonThemeTrait.php b/apps/theming/lib/Themes/CommonThemeTrait.php
index 5a389c8533f..0b033b3fac9 100644
--- a/apps/theming/lib/Themes/CommonThemeTrait.php
+++ b/apps/theming/lib/Themes/CommonThemeTrait.php
@@ -40,7 +40,7 @@ trait CommonThemeTrait {
*/
protected function generatePrimaryVariables(string $colorMainBackground, string $colorMainText): array {
$isBrightColor = $this->util->isBrightColor($colorMainBackground);
- $colorPrimaryElement = $this->util->elementColor($this->primaryColor, $isBrightColor);
+ $colorPrimaryElement = $this->util->elementColor($this->primaryColor, $isBrightColor, $colorMainBackground);
$colorPrimaryLight = $this->util->mix($colorPrimaryElement, $colorMainBackground, -80);
$colorPrimaryElementLight = $this->util->mix($colorPrimaryElement, $colorMainBackground, -80);
diff --git a/apps/theming/lib/Util.php b/apps/theming/lib/Util.php
index 951b07bfa2a..c4f0123460e 100644
--- a/apps/theming/lib/Util.php
+++ b/apps/theming/lib/Util.php
@@ -57,7 +57,7 @@ class Util {
* @return bool
*/
public function invertTextColor(string $color): bool {
- return $this->isBrightColor($color);
+ return $this->colorContrast($color, '#ffffff') < 4.5;
}
/**
@@ -81,7 +81,28 @@ class Util {
* @param ?bool $brightBackground
* @return string
*/
- public function elementColor($color, ?bool $brightBackground = null) {
+ public function elementColor($color, ?bool $brightBackground = null, ?string $backgroundColor = null) {
+ if ($backgroundColor !== null) {
+ $brightBackground = $brightBackground ?? $this->isBrightColor($backgroundColor);
+ // Minimal amount that is possible to change the luminance
+ $epsilon = 1.0 / 255.0;
+ // Current iteration to prevent infinite loops
+ $iteration = 0;
+ // We need to keep blurred backgrounds in mind which might be mixed with the background
+ $blurredBackground = $this->mix($backgroundColor, $brightBackground ? $color : '#ffffff', 66);
+ $contrast = $this->colorContrast($color, $blurredBackground);
+
+ // Min. element contrast is 3:1 but we need to keep hover states in mind -> min 3.2:1
+ while ($contrast < 3.2 && $iteration++ < 100) {
+ $hsl = Color::hexToHsl($color);
+ $hsl['L'] = max(0, min(1, $hsl['L'] + ($brightBackground ? -$epsilon : $epsilon)));
+ $color = '#' . Color::hslToHex($hsl);
+ $contrast = $this->colorContrast($color, $blurredBackground);
+ }
+ return $color;
+ }
+
+ // Fallback for legacy calling
$luminance = $this->calculateLuminance($color);
if ($brightBackground !== false && $luminance > 0.8) {
@@ -139,12 +160,38 @@ class Util {
}
/**
+ * Calculate the Luma according to WCAG 2
+ * http://www.w3.org/TR/WCAG20/#relativeluminancedef
* @param string $color rgb color value
* @return float
*/
public function calculateLuma(string $color): float {
- [$red, $green, $blue] = $this->hexToRGB($color);
- return (0.2126 * $red + 0.7152 * $green + 0.0722 * $blue) / 255;
+ $rgb = $this->hexToRGB($color);
+
+ // Normalize the values by converting to float and applying the rules from WCAG2.0
+ $rgb = array_map(function (int $color) {
+ $color = $color / 255.0;
+ if ($color <= 0.03928) {
+ return $color / 12.92;
+ } else {
+ return pow((($color + 0.055) / 1.055), 2.4);
+ }
+ }, $rgb);
+
+ [$red, $green, $blue] = $rgb;
+ return (0.2126 * $red + 0.7152 * $green + 0.0722 * $blue);
+ }
+
+ /**
+ * Calculat the color contrast according to WCAG 2
+ * http://www.w3.org/TR/WCAG20/#contrast-ratiodef
+ * @param string $color1 The first color
+ * @param string $color2 The second color
+ */
+ public function colorContrast(string $color1, string $color2): float {
+ $luminance1 = $this->calculateLuma($color1) + 0.05;
+ $luminance2 = $this->calculateLuma($color2) + 0.05;
+ return max($luminance1, $luminance2) / min($luminance1, $luminance2);
}
/**
diff --git a/apps/theming/tests/UtilTest.php b/apps/theming/tests/UtilTest.php
index 0d986a2b112..857e9fff6a5 100644
--- a/apps/theming/tests/UtilTest.php
+++ b/apps/theming/tests/UtilTest.php
@@ -35,19 +35,20 @@ use OCP\Files\NotFoundException;
use OCP\Files\SimpleFS\ISimpleFile;
use OCP\Files\SimpleFS\ISimpleFolder;
use OCP\IConfig;
+use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
class UtilTest extends TestCase {
/** @var Util */
protected $util;
- /** @var IConfig */
+ /** @var IConfig|MockObject */
protected $config;
- /** @var IAppData */
+ /** @var IAppData|MockObject */
protected $appData;
- /** @var IAppManager */
+ /** @var IAppManager|MockObject */
protected $appManager;
- /** @var ImageManager */
+ /** @var ImageManager|MockObject */
protected $imageManager;
protected function setUp(): void {
@@ -59,11 +60,29 @@ class UtilTest extends TestCase {
$this->util = new Util($this->config, $this->appManager, $this->appData, $this->imageManager);
}
+ public function dataColorContrast() {
+ return [
+ ['#ffffff', '#FFFFFF', 1],
+ ['#000000', '#000000', 1],
+ ['#ffffff', '#000000', 21],
+ ['#000000', '#FFFFFF', 21],
+ ['#9E9E9E', '#353535', 4.578],
+ ['#353535', '#9E9E9E', 4.578],
+ ];
+ }
+
+ /**
+ * @dataProvider dataColorContrast
+ */
+ public function testColorContrast(string $color1, string $color2, $contrast) {
+ $this->assertEqualsWithDelta($contrast, $this->util->colorContrast($color1, $color2), .001);
+ }
+
public function dataInvertTextColor() {
return [
['#ffffff', true],
['#000000', false],
- ['#0082C9', false],
+ ['#00679e', false],
['#ffff00', true],
];
}