diff --git a/commons/src/main/kotlin/org/fossify/commons/extensions/Int.kt b/commons/src/main/kotlin/org/fossify/commons/extensions/Int.kt index 42de9253a..9f5f383f6 100644 --- a/commons/src/main/kotlin/org/fossify/commons/extensions/Int.kt +++ b/commons/src/main/kotlin/org/fossify/commons/extensions/Int.kt @@ -9,22 +9,59 @@ import android.os.Looper import androidx.core.graphics.ColorUtils import androidx.core.os.postDelayed import org.fossify.commons.helpers.DARK_GREY +import org.fossify.commons.helpers.MAX_ALPHA_INT import org.fossify.commons.helpers.WCAG_AA_NORMAL +import org.fossify.commons.helpers.LUMINANCE_OFFSET import java.text.DecimalFormat import java.util.Locale import java.util.Random import kotlin.math.log10 import kotlin.math.pow +import kotlin.math.sqrt fun Int.getContrastColor(): Int { return getContrastColor(DARK_GREY, Color.WHITE) } -fun Int.getContrastColor(colorFirst: Int, colorSecond: Int): Int { - val contrastFirst = ColorUtils.calculateContrast(colorFirst, this) - val contrastSecond = ColorUtils.calculateContrast(colorSecond, this) +fun Int.getContrastColor(firstColor: Int, secondColor: Int): Int { + // Opaque background + if (Color.alpha(this) == MAX_ALPHA_INT) { + val contrastFirstColor = ColorUtils.calculateContrast(firstColor, this) + val contrastSecondColor = ColorUtils.calculateContrast(secondColor, this) - return if (contrastFirst >= contrastSecond) colorFirst else colorSecond + return if (contrastFirstColor >= contrastSecondColor) firstColor else secondColor + } + + // Translucent background: fallback heuristic + val luminanceBackground = ColorUtils.calculateLuminance(this) + val luminanceFirstColor = ColorUtils.calculateLuminance(firstColor) + val luminanceSecondColor = ColorUtils.calculateLuminance(secondColor) + + val lightColor: Int + val darkColor: Int + val luminanceLight: Double + val luminanceDark: Double + + if (luminanceFirstColor >= luminanceSecondColor) { + lightColor = firstColor + darkColor = secondColor + luminanceLight = luminanceFirstColor + luminanceDark = luminanceSecondColor + } else { + lightColor = secondColor + darkColor = firstColor + luminanceLight = luminanceSecondColor + luminanceDark = luminanceFirstColor + } + + // Compute crossover luminance where both candidates have equal WCAG contrast + // against a (hypothetical) opaque background of luminance L: + // (L + 0.05)^2 = (lLight + 0.05) * (lDark + 0.05) + val threshold = sqrt((luminanceLight + LUMINANCE_OFFSET) * (luminanceDark + LUMINANCE_OFFSET)) - LUMINANCE_OFFSET + + // If background is lighter than the threshold -> choose darker foreground, + // else choose lighter foreground. + return if (luminanceBackground >= threshold) darkColor else lightColor } fun Int.toHex() = String.format("#%06X", 0xFFFFFF and this).uppercase(Locale.getDefault()) diff --git a/commons/src/main/kotlin/org/fossify/commons/helpers/Constants.kt b/commons/src/main/kotlin/org/fossify/commons/helpers/Constants.kt index 0813dd571..64ac18970 100644 --- a/commons/src/main/kotlin/org/fossify/commons/helpers/Constants.kt +++ b/commons/src/main/kotlin/org/fossify/commons/helpers/Constants.kt @@ -60,10 +60,13 @@ const val HIGHER_ALPHA = 0.75f // alpha values on a scale 0 - 255 const val LOWER_ALPHA_INT = 30 const val MEDIUM_ALPHA_INT = 90 +const val MAX_ALPHA_INT = 255 const val WCAG_AA_NORMAL = 4.5 const val WCAG_AA_LARGE = 3.0 +const val LUMINANCE_OFFSET = 0.05 + const val HOUR_MINUTES = 60 const val DAY_MINUTES = 24 * HOUR_MINUTES const val WEEK_MINUTES = DAY_MINUTES * 7