Module:Color contrast
Jump to navigation
Jump to search
Documentation for this module may be created at Module:Color contrast/doc
local p = {}
local HTMLcolor = mw.loadData('Module:Color contrast/colors')
-- Helper function to convert sRGB component to linearized value
local function sRGB(v)
return (v <= 0.03928) and (v / 12.92) or math.pow((v + 0.055) / 1.055, 2.4)
end
-- Calculate luminance from RGB values
local function rgbdec2lum(R, G, B)
if R >= 0 and R < 256 and G >= 0 and G < 256 and B >= 0 and B < 256 then
return 0.2126 * sRGB(R / 255) + 0.7152 * sRGB(G / 255) + 0.0722 * sRGB(B / 255)
else
return ''
end
end
-- Convert HSL to luminance
local function hsl2lum(h, s, l)
if h >= 0 and h < 360 and s >= 0 and s <= 1 and l >= 0 and l <= 1 then
local c = (1 - math.abs(2 * l - 1)) * s
local x = c * (1 - math.abs(h / 60 % 2 - 1))
local m = l - c / 2
local r, g, b = m, m, m
if h < 60 then
r = r + c; g = g + x
elseif h < 120 then
r = r + x; g = g + c
elseif h < 180 then
g = g + c; b = b + x
elseif h < 240 then
g = g + x; b = b + c
elseif h < 300 then
r = r + x; b = b + c
else
r = r + c; b = b + x
end
return rgbdec2lum(255 * r, 255 * g, 255 * b)
else
return ''
end
end
-- Convert a color string to luminance
local function color2lum(c)
if not c then return '' end
-- Clean up color input
c = c:gsub("#", "#"):match('^%s*(.-)[%s;]*$'):lower()
c = mw.text.unstripNoWiki(c)
-- Try to look up color in predefined colors
local L = HTMLcolor[c]
if L then return L end
-- HSL to luminance
if mw.ustring.match(c, '^hsl%(%s*[0-9.]+%s*,%s*[0-9.]+%%%s*,%s*[0-9.]+%%%s*%)$') then
local h, s, l = mw.ustring.match(c, '^hsl%(%s*([0-9.]+)%s*,%s*([0-9.]+)%%%s*,%s*([0-9.]+)%%%s*%)$')
return hsl2lum(tonumber(h), tonumber(s) / 100, tonumber(l) / 100)
end
-- RGB to luminance
if mw.ustring.match(c, '^rgb%(%s*[0-9]+%s*,%s*[0-9]+%s*,%s*[0-9]+%s*%)$') then
local R, G, B = mw.ustring.match(c, '^rgb%(%s*([0-9]+)%s*,%s*([0-9]+)%s*,%s*([0-9]+)%s*%)$')
return rgbdec2lum(tonumber(R), tonumber(G), tonumber(B))
end
-- RGB percentage to luminance
if mw.ustring.match(c, '^rgb%(%s*[0-9.]+%%%s*,%s*[0-9.]+%%%s*,%s*[0-9.]+%%%s*%)$') then
local R, G, B = mw.ustring.match(c, '^rgb%(%s*([0-9.]+)%%%s*,%s*([0-9.]+)%%%s*,%s*([0-9.]+)%%%s*%)$')
return rgbdec2lum(255 * tonumber(R) / 100, 255 * tonumber(G) / 100, 255 * tonumber(B) / 100)
end
-- Hex color to luminance
c = mw.ustring.match(c, '^[%s#]*([a-f0-9]*)[%s]*$')
if c then
local cs = mw.text.split(c, '')
if #cs == 6 then
local R = 16 * tonumber('0x' .. cs[1]) + tonumber('0x' .. cs[2])
local G = 16 * tonumber('0x' .. cs[3]) + tonumber('0x' .. cs[4])
local B = 16 * tonumber('0x' .. cs[5]) + tonumber('0x' .. cs[6])
return rgbdec2lum(R, G, B)
elseif #cs == 3 then
local R = 16 * tonumber('0x' .. cs[1]) + tonumber('0x' .. cs[1])
local G = 16 * tonumber('0x' .. cs[2]) + tonumber('0x' .. cs[2])
local B = 16 * tonumber('0x' .. cs[3]) + tonumber('0x' .. cs[3])
return rgbdec2lum(R, G, B)
end
end
return ''
end
-- Calculate contrast ratio between two luminance values
local function contrast_ratio(lum1, lum2)
if lum2 > lum1 then lum1, lum2 = lum2, lum1 end
return (lum1 + 0.05) / (lum2 + 0.05)
end
-- Exported functions
function p._lum(color)
return color2lum(color)
end
function p._ratio(args)
local lum1 = color2lum(args[1])
local lum2 = color2lum(args[2])
return (lum1 ~= '' and lum2 ~= '') and contrast_ratio(lum1, lum2) or args['error'] or '?'
end
function p._greatercontrast(args)
local bias = tonumber(args['bias'] or '0') or 0
local css = args['css'] and args['css'] ~= ''
local lum1 = color2lum(args[1] or '')
local lum2 = color2lum(args[2] or '#FFFFFF')
local lum3 = color2lum(args[3] or '#000000')
local ratio1 = contrast_ratio(lum1, lum2)
local ratio2 = contrast_ratio(lum1, lum3)
if css then
local c1 = args[1] or ''
if mw.ustring.match(c1, '^[A-Fa-f0-9]+$') then c1 = '#' .. c1 end
if mw.ustring.match(args[2], '^[A-Fa-f0-9]+$') then c2 = '#' .. args[2] end
if mw.ustring.match(args[3], '^[A-Fa-f0-9]+$') then c3 = '#' .. args[3] end
return 'background-color:' .. c1 .. '; color:' .. ((ratio1 + bias > ratio2) and args[2] or args[3]) .. ';'
end
return (ratio1 + bias > ratio2) and args[2] or args[3]
end
function p._styleratio(args)
local style = (args[1] or ''):lower()
local bg, fg = 'white', 'black'
local lum_bg, lum_fg = 1, 0
if args[2] then
local lum = color2lum(args[2])
if lum ~= '' then bg, lum_bg = args[2], lum end
end
if args[3] then
local lum = color2lum(args[3])
if lum ~= '' then fg, lum_fg = args[3], lum end
end
local slist = mw.text.split(mw.ustring.gsub(mw.ustring.gsub(style, '&#[Xx]23;', '#'), '#', '#'), ';')
for _, s in ipairs(slist) do
local k, v = s:match('^[%s]*(.-):%s*(.-)%s*$')
if k == 'background' or k == 'background-color' then
local lum = color2lum(v)
if lum ~= '' then bg, lum_bg = v, lum end
elseif k == 'color' then
local lum = color2lum(v)
if lum ~= '' then fg, lum_fg = v, lum end
end
end
return contrast_ratio(lum_bg, lum_fg)
end
-- Wrapper functions
function p.lum(frame)
local color = frame.args[1] or frame:getParent().args[1]
return p._lum(color)
end
function p.ratio(frame)
local args = frame.args[1] and frame.args or frame:getParent().args
return p._ratio(args)
end
function p.styleratio(frame)
local args = frame.args[1] and frame.args or frame:getParent().args
return p._styleratio(args)
end
function p.greatercontrast(frame)
local args = frame.args[1] and frame.args or frame:getParent().args
return p._greatercontrast(args)
end
return p