From 99776dd19069c1eab54abd023421aec893045731 Mon Sep 17 00:00:00 2001 From: Tobias Bengfort Date: Tue, 15 Mar 2016 20:57:16 +0100 Subject: [PATCH 1/2] add test cases for contrast with transparency --- test/css/functions.css | 2 ++ test/less/functions.less | 2 ++ 2 files changed, 4 insertions(+) diff --git a/test/css/functions.css b/test/css/functions.css index 4f8061225..400188deb 100644 --- a/test/css/functions.css +++ b/test/css/functions.css @@ -54,6 +54,8 @@ contrast-dark-thresh-per: #eeeeee; contrast-high-thresh-per: #eeeeee; contrast-low-thresh-per: #eeeeee; + contrast-high-alpha: rgba(0, 0, 0, 0.9); + contrast-low-alpha: #eeeeee; replace: "Hello, World!"; replace-captured: "This is a new string."; replace-with-flags: "2 + 2 = 4"; diff --git a/test/less/functions.less b/test/less/functions.less index 4866bdca7..cdc3aafbe 100644 --- a/test/less/functions.less +++ b/test/less/functions.less @@ -58,6 +58,8 @@ contrast-dark-thresh-per: contrast(#000, #111111, #eeeeee, 50%); contrast-high-thresh-per: contrast(#555, #111111, #eeeeee, 60%); contrast-low-thresh-per: contrast(#555, #111111, #eeeeee, 9%); + contrast-high-alpha: contrast(#aaa, rgba(0,0,0,0.9), #eeeeee); + contrast-low-alpha: contrast(#aaa, rgba(0,0,0,0.1), #eeeeee); replace: replace("Hello, Mars.", "Mars\.", "World!"); replace-captured: replace("This is a string.", "(string)\.$", "new $1."); replace-with-flags: replace("One + one = 4", "one", "2", "gi"); From 41e296a1dcd552e757b43244869832c83c5746ac Mon Sep 17 00:00:00 2001 From: Tobias Bengfort Date: Tue, 15 Mar 2016 21:00:09 +0100 Subject: [PATCH 2/2] implement symmetric minimal contrast --- lib/less/functions/color.js | 59 +++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/lib/less/functions/color.js b/lib/less/functions/color.js index db72f6e02..dbac7ae4e 100644 --- a/lib/less/functions/color.js +++ b/lib/less/functions/color.js @@ -30,6 +30,48 @@ function scaled(n, size) { return number(n); } } +function alphaBlend(color1, color2) { + var ab = color1.alpha, // backdrop + as = color2.alpha, // source + ar, r = []; // result + + ar = as + ab * (1 - as); + + if (ar) { + for (var i = 0; i < 3; i++) { + r[i] = (as * color2.rgb[i] + ab * (1 - as) * color1.rgb[i]) / ar; + } + return new Color(r, ar); + } else { + return color2; + } +} +function contrastBase(bg, fg) { + var lbg = bg.luma(), + lfg = alphaBlend(bg, fg).luma(); + return (Math.max(lbg, lfg) + 0.05) / (Math.min(lbg, lfg) + 0.05); +} +function contrastMin(bg, fg) { + // Calculate the minimum possible contrast between two colors. + // This is identical to contrastBase except for cases in which + // bg.alpha < 1. + var bgBlack = alphaBlend(colorFunctions.rgb(0, 0, 0), bg), + bgWhite = alphaBlend(colorFunctions.rgb(255, 255, 255), bg), + lfg = fg.luma(); + + if (bgWhite.luma() < lfg) { + return contrastBase(bgWhite, fg); + } else if (bgBlack.luma() > lfg) { + return contrastBase(bgBlack, fg); + } else { + return 1; + } +} +function contrast(color1, color2) { + var c1 = contrastMin(color1, color2); + var c2 = contrastMin(color2, color1); + return (c1 + c2) / 2; +} colorFunctions = { rgb: function (r, g, b) { return colorFunctions.rgba(r, g, b, 1.0); @@ -282,22 +324,7 @@ colorFunctions = { if (typeof color2 === 'undefined') { color2 = colorFunctions.rgba(255, 255, 255, 1.0); } - var contrast1, contrast2; - var luma = color.luma(); - var luma1 = color1.luma(); - var luma2 = color2.luma(); - // Calculate contrast ratios for each color - if (luma > luma1) { - contrast1 = (luma + 0.05) / (luma1 + 0.05); - } else { - contrast1 = (luma1 + 0.05) / (luma + 0.05); - } - if (luma > luma2) { - contrast2 = (luma + 0.05) / (luma2 + 0.05); - } else { - contrast2 = (luma2 + 0.05) / (luma + 0.05); - } - if (contrast1 > contrast2) { + if (contrast(color, color1) > contrast(color, color2)) { return color1; } else { return color2;