Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>tags/v29.0.0beta1
*/ | */ | ||||
protected function generatePrimaryVariables(string $colorMainBackground, string $colorMainText): array { | protected function generatePrimaryVariables(string $colorMainBackground, string $colorMainText): array { | ||||
$isBrightColor = $this->util->isBrightColor($colorMainBackground); | $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); | $colorPrimaryLight = $this->util->mix($colorPrimaryElement, $colorMainBackground, -80); | ||||
$colorPrimaryElementLight = $this->util->mix($colorPrimaryElement, $colorMainBackground, -80); | $colorPrimaryElementLight = $this->util->mix($colorPrimaryElement, $colorMainBackground, -80); | ||||
* @return bool | * @return bool | ||||
*/ | */ | ||||
public function invertTextColor(string $color): bool { | public function invertTextColor(string $color): bool { | ||||
return $this->isBrightColor($color); | |||||
return $this->colorContrast($color, '#ffffff') < 4.5; | |||||
} | } | ||||
/** | /** | ||||
* @param ?bool $brightBackground | * @param ?bool $brightBackground | ||||
* @return string | * @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); | $luminance = $this->calculateLuminance($color); | ||||
if ($brightBackground !== false && $luminance > 0.8) { | if ($brightBackground !== false && $luminance > 0.8) { | ||||
} | } | ||||
/** | /** | ||||
* Calculate the Luma according to WCAG 2 | |||||
* http://www.w3.org/TR/WCAG20/#relativeluminancedef | |||||
* @param string $color rgb color value | * @param string $color rgb color value | ||||
* @return float | * @return float | ||||
*/ | */ | ||||
public function calculateLuma(string $color): 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); | |||||
} | } | ||||
/** | /** |
use OCP\Files\SimpleFS\ISimpleFile; | use OCP\Files\SimpleFS\ISimpleFile; | ||||
use OCP\Files\SimpleFS\ISimpleFolder; | use OCP\Files\SimpleFS\ISimpleFolder; | ||||
use OCP\IConfig; | use OCP\IConfig; | ||||
use PHPUnit\Framework\MockObject\MockObject; | |||||
use Test\TestCase; | use Test\TestCase; | ||||
class UtilTest extends TestCase { | class UtilTest extends TestCase { | ||||
/** @var Util */ | /** @var Util */ | ||||
protected $util; | protected $util; | ||||
/** @var IConfig */ | |||||
/** @var IConfig|MockObject */ | |||||
protected $config; | protected $config; | ||||
/** @var IAppData */ | |||||
/** @var IAppData|MockObject */ | |||||
protected $appData; | protected $appData; | ||||
/** @var IAppManager */ | |||||
/** @var IAppManager|MockObject */ | |||||
protected $appManager; | protected $appManager; | ||||
/** @var ImageManager */ | |||||
/** @var ImageManager|MockObject */ | |||||
protected $imageManager; | protected $imageManager; | ||||
protected function setUp(): void { | protected function setUp(): void { | ||||
$this->util = new Util($this->config, $this->appManager, $this->appData, $this->imageManager); | $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() { | public function dataInvertTextColor() { | ||||
return [ | return [ | ||||
['#ffffff', true], | ['#ffffff', true], | ||||
['#000000', false], | ['#000000', false], | ||||
['#0082C9', false], | |||||
['#00679e', false], | |||||
['#ffff00', true], | ['#ffff00', true], | ||||
]; | ]; | ||||
} | } |