@@ -1145,12 +1145,38 @@ public function fileTypeIcon(string $type): string
1145
1145
return 'fas fa-file- ' . $ iconName ;
1146
1146
}
1147
1147
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
1149
1179
{
1150
- $ rgb = Utils::convertToHex ($ problem ->getColor () ?? '#ffffff ' );
1151
- if ($ grayedOut || empty ($ rgb )) {
1152
- $ rgb = Utils::convertToHex ('whitesmoke ' );
1153
- }
1154
1180
$ background = Utils::parseHexColor ($ rgb );
1155
1181
1156
1182
// Pick a border that's a bit darker.
@@ -1160,8 +1186,24 @@ public function problemBadge(ContestProblem $problem, bool $grayedOut = false):
1160
1186
$ darker [2 ] = max ($ darker [2 ] - 64 , 0 );
1161
1187
$ border = Utils::rgbToHex ($ darker );
1162
1188
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
+
1165
1207
if ($ grayedOut ) {
1166
1208
$ foreground = 'silver ' ;
1167
1209
$ border = 'linen ' ;
@@ -1181,17 +1223,9 @@ public function problemBadgeMaybe(ContestProblem $problem, ScoreboardMatrixItem
1181
1223
if (!$ matrixItem ->isCorrect || empty ($ rgb )) {
1182
1224
$ rgb = Utils::convertToHex ('whitesmoke ' );
1183
1225
}
1184
- $ background = Utils::parseHexColor ($ rgb );
1185
1226
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 );
1192
1228
1193
- // Pick the foreground text color based on the background color.
1194
- $ foreground = ($ background [0 ] + $ background [1 ] + $ background [2 ] > 450 ) ? '#000000 ' : '#ffffff ' ;
1195
1229
if (!$ matrixItem ->isCorrect ) {
1196
1230
$ foreground = 'silver ' ;
1197
1231
$ border = 'linen ' ;
0 commit comments