@@ -1145,12 +1145,38 @@ public function fileTypeIcon(string $type): string
11451145 return 'fas fa-file- ' . $ iconName ;
11461146 }
11471147
1148- public function problemBadge (ContestProblem $ problem , bool $ grayedOut = false ): string
1148+ private function relativeLuminance (string $ rgb ): float
1149+ {
1150+ // See https://en.wikipedia.org/wiki/Relative_luminance
1151+ [$ r , $ g , $ b ] = Utils::parseHexColor ($ rgb );
1152+
1153+ [$ lr , $ lg , $ lb ] = [
1154+ pow ($ r / 255 , 2.4 ),
1155+ pow ($ g / 255 , 2.4 ),
1156+ pow ($ b / 255 , 2.4 ),
1157+ ];
1158+
1159+ return 0.2126 * $ lr + 0.7152 * $ lg + 0.0722 * $ lb ;
1160+ }
1161+
1162+ private function apcaContrast (string $ fgColor , string $ bgColor ): float
1163+ {
1164+ // Based on WCAG 3.x (https://www.w3.org/TR/wcag-3.0/)
1165+ $ luminanceForeground = $ this ->relativeLuminance ($ fgColor );
1166+ $ luminanceBackground = $ this ->relativeLuminance ($ bgColor );
1167+
1168+ $ contrast = ($ luminanceBackground > $ luminanceForeground )
1169+ ? (pow ($ luminanceBackground , 0.56 ) - pow ($ luminanceForeground , 0.57 )) * 1.14
1170+ : (pow ($ luminanceBackground , 0.65 ) - pow ($ luminanceForeground , 0.62 )) * 1.14 ;
1171+
1172+ return round ($ contrast * 100 , 2 );
1173+ }
1174+
1175+ /**
1176+ * @return array{string, string}
1177+ */
1178+ private function hexToForegroundAndBorder (string $ rgb ): array
11491179 {
1150- $ rgb = Utils::convertToHex ($ problem ->getColor () ?? '#ffffff ' );
1151- if ($ grayedOut || empty ($ rgb )) {
1152- $ rgb = Utils::convertToHex ('whitesmoke ' );
1153- }
11541180 $ background = Utils::parseHexColor ($ rgb );
11551181
11561182 // Pick a border that's a bit darker.
@@ -1160,8 +1186,24 @@ public function problemBadge(ContestProblem $problem, bool $grayedOut = false):
11601186 $ darker [2 ] = max ($ darker [2 ] - 64 , 0 );
11611187 $ border = Utils::rgbToHex ($ darker );
11621188
1163- // Pick the foreground text color based on the background color.
1164- $ foreground = ($ background [0 ] + $ background [1 ] + $ background [2 ] > 450 ) ? '#000000 ' : '#ffffff ' ;
1189+ // Pick the text color with the biggest absolute contrast.
1190+ $ contrastWithWhite = $ this ->apcaContrast ('#ffffff ' , $ rgb );
1191+ $ contrastWithBlack = $ this ->apcaContrast ('#000000 ' , $ rgb );
1192+
1193+ $ foreground = (abs ($ contrastWithBlack ) > abs ($ contrastWithWhite )) ? '#000000 ' : '#ffffff ' ;
1194+
1195+ return [$ foreground , $ border ];
1196+ }
1197+
1198+ public function problemBadge (ContestProblem $ problem , bool $ grayedOut = false ): string
1199+ {
1200+ $ rgb = Utils::convertToHex ($ problem ->getColor () ?? '#ffffff ' );
1201+ if ($ grayedOut || empty ($ rgb )) {
1202+ $ rgb = Utils::convertToHex ('whitesmoke ' );
1203+ }
1204+
1205+ [$ foreground , $ border ] = $ this ->hexToForegroundAndBorder ($ rgb );
1206+
11651207 if ($ grayedOut ) {
11661208 $ foreground = 'silver ' ;
11671209 $ border = 'linen ' ;
@@ -1181,17 +1223,9 @@ public function problemBadgeMaybe(ContestProblem $problem, ScoreboardMatrixItem
11811223 if (!$ matrixItem ->isCorrect || empty ($ rgb )) {
11821224 $ rgb = Utils::convertToHex ('whitesmoke ' );
11831225 }
1184- $ background = Utils::parseHexColor ($ rgb );
11851226
1186- // Pick a border that's a bit darker.
1187- $ darker = $ background ;
1188- $ darker [0 ] = max ($ darker [0 ] - 64 , 0 );
1189- $ darker [1 ] = max ($ darker [1 ] - 64 , 0 );
1190- $ darker [2 ] = max ($ darker [2 ] - 64 , 0 );
1191- $ border = Utils::rgbToHex ($ darker );
1227+ [$ foreground , $ border ] = $ this ->hexToForegroundAndBorder ($ rgb );
11921228
1193- // Pick the foreground text color based on the background color.
1194- $ foreground = ($ background [0 ] + $ background [1 ] + $ background [2 ] > 450 ) ? '#000000 ' : '#ffffff ' ;
11951229 if (!$ matrixItem ->isCorrect ) {
11961230 $ foreground = 'silver ' ;
11971231 $ border = 'linen ' ;
0 commit comments