Module:Color
![]() | This module is rated as ready for general use. It has reached a mature form and is thought to be relatively bug-free and ready for use wherever appropriate. It is ready to mention on help pages and other Wikipedia resources as an option for new users to learn. To reduce server load and bad output, it should be improved by sandbox testing rather than repeated trial-and-error editing. |
![]() | This module is subject to page protection. It is a highly visible module in use by a very large number of pages, or is substituted very frequently. Because vandalism or mistakes would affect many pages, and even trivial editing might cause substantial load on the servers, it is protected from editing. |
![]() | This Lua module is used on approximately 620 pages and changes may be widely noticed. Test changes in the module's /sandbox or /testcases subpages, or in your own module sandbox. Consider discussing changes on the talk page before implementing them. |
This module is used primarily by {{Infobox color}}, eliminating the need for external color converters and preventing mismatch between color coordinates.
Usage
To use this module, you may use one of the above listed templates or invoke the module directly. All functions that accept hexadecimal triplets also handle the shorthand three-digit format.
To convert a hexadecimal triplet to an RGB triplet as comma-separated values:
{{#invoke:Color|hexToRgbTriplet|color}}
To convert a hexadecimal triplet to the CMYK color model without a color profile (which is a very bad idea!):
{{#invoke:Color|hexToCmyk|color|precision=?|pctsign=?}}
To convert a hexadecimal triplet to HSL or HSV:
{{#invoke:Color|hexToHsl|color|precision=?}}
{{#invoke:Color|hexToHsv|color|precision=?}}
To convert a hexadecimal triplet to the perceptual CIELChuv color space:
{{#invoke:Color|hexToCielch|color|precision=?}}
To mix two colors in the more physically correct linear RGB space:
{{#invoke:Color|hexMix|color1|color2|proportion|min=?|max=?}}
To convert an RGB triplet to a hex code:
{{#invoke:Color|rgbTripletToHex|r|g|b}}
The following parameters are optional:
precision
: defaults to0
(zero)pctsign
: set to0
(zero) to suppress percent signs in the generated outputproportion
: proportion ofcolor2
, defaults to 50min
: minimum value of proportion range, defaults to 0max
: maximum value of proportion range, defaults to 100
local p = {}
local function hexToRgb(color)
if (#color == 6) then
return {
r = tonumber(string.sub(color, 1, 2), 16),
g = tonumber(string.sub(color, 3, 4), 16),
b = tonumber(string.sub(color, 5, 6), 16)
}
elseif (#color == 3) then
return {
r = 17 * tonumber(string.sub(color, 1, 1), 16),
g = 17 * tonumber(string.sub(color, 2, 2), 16),
b = 17 * tonumber(string.sub(color, 3, 3), 16)
}
end
error("Invalid hexadecimal color " .. color, 1)
end
local function rgbToHsl(r, g, b)
if (r > 255 or g > 255 or b > 255 or r < 0 or g < 0 or b < 0) then
error("Color level out of bounds")
end
channelMax = math.max(r, g, b)
channelMin = math.min(r, g, b)
range = channelMax - channelMin
if (range == 0) then
h = 0
elseif (channelMax == r) then
h = 60 * ((g - b) / range)
elseif (channelMax == g) then
h = 60 * (2 + (b - r) / range)
else
h = 60 * (4 + (r - g) / range)
end
if (h < 0) then
h = 360 + h
end
L = channelMax + channelMin
if (L == 0 or L == 510) then
s = 0
else
s = 100 * range / math.min(L, 510 - L)
end
return { h = h, s = s, l = L * 50 / 255 }
end
local function rgbToHsv(r, g, b)
if (r > 255 or g > 255 or b > 255 or r < 0 or g < 0 or b < 0) then
error("Color level out of bounds")
end
channelMax = math.max(r, g, b)
channelMin = math.min(r, g, b)
range = channelMax - channelMin
if (range == 0) then
h = 0
elseif (channelMax == r) then
h = 60 * ((g - b) / range)
elseif (channelMax == g) then
h = 60 * (2 + (b - r) / range)
else
h = 60 * (4 + (r - g) / range)
end
if (h < 0) then
h = 360 + h
end
if (channelMax == 0) then
s = 0
else
s = 100 * range / channelMax
end
return { h = h, s = s, v = channelMax * 100 / 255 }
end
-- c in [0, 255], condition tweaked for no discontinuity
-- http://entropymine.com/imageworsener/srgbformula/
local function inverseCompanding(c)
if (c > 10.314300250662591) then
return math.pow((c + 14.025) / 269.025, 2.4)
else
return c / 3294.6
end
end
local function srgbToCielchuvD65o2deg(r, g, b)
if (r > 255 or g > 255 or b > 255 or r < 0 or g < 0 or b < 0) then
error("Color level out of bounds")
end
R = inverseCompanding(r)
G = inverseCompanding(g)
B = inverseCompanding(b)
-- https://github.com/w3c/csswg-drafts/issues/5922
X = 0.1804807884018343 * B + 0.357584339383878 * G + 0.41239079926595934 * R
Y = 0.07219231536073371 * B + 0.21263900587151027 * R + 0.715168678767756 * G
Z = 0.01933081871559182 * R + 0.11919477979462598 * G + 0.9505321522496607 * B
if (Y > 0.00885645167903563082) then
L = 116 * math.pow(Y, 1/3) - 16
else
L = Y * 903.2962962962962962963
end
if (r == g and g == b) then
C = 0
h = 0
else
d = X + 3 * Z + 15 * Y
if (d == 0) then
C = 0
h = 0
else
-- 0.19783... and 0.4631... computed with extra precision from (X,Y,Z) when (R,G,B) = (1,1,1),
-- in which case (u,v) ≈ (0,0)
u = 13 * L * (4 * X / d - 0.19783000664283678994)
v = 13 * L * (9 * Y / d - 0.46831999493879099801)
h = math.atan2(v, u) * 57.2957795130823208768
if (h < 0) then
h = h + 360
elseif (h == 0) then
h = 0 -- ensure zero is positive
end
C = math.sqrt(u * u + v * v)
if (C == 0) then
C = 0
h = 0
end
end
end
return { L = L, C = C, h = h }
end
local function formatAngle(value, p)
local vs = string.format("%." .. p .. "f", value)
local zeros
if (p > 0) then
zeros = "." .. string.rep("0", p)
else
zeros = ""
end
if (vs == "360" .. zeros) then
vs = "0" .. zeros -- handle rounding to 360
end
return vs .. "°"
end
function p.hexToRgbTriplet(frame)
local args = frame.args or frame:getParent().args
local hex = args[1]
if (hex) then
local rgb = hexToRgb(hex)
return rgb.r .. ', ' .. rgb.g .. ', ' .. rgb.b
else
return ""
end
end
function p.hexToHsl(frame)
local args = frame.args or frame:getParent().args
local hex = args[1]
local p = tonumber(args.precision) or 0
if (hex) then
local rgb = hexToRgb(hex)
local hsl = rgbToHsl(rgb.r, rgb.g, rgb.b)
return formatAngle(hsl.h, p) .. string.format(", %." .. p .. "f%%, %." .. p .. "f%%", hsl.s, hsl.l)
else
return ""
end
end
function p.hexToHsv(frame)
local args = frame.args or frame:getParent().args
local hex = args[1]
local p = tonumber(args.precision) or 0
if (hex) then
local rgb = hexToRgb(hex)
local hsv = rgbToHsv(rgb.r, rgb.g, rgb.b)
return formatAngle(hsv.h, p) .. string.format(", %." .. p .. "f%%, %." .. p .. "f%%", hsv.s, hsv.v)
else
return ""
end
end
function p.hexToCielch(frame)
local args = frame.args or frame:getParent().args
local hex = args[1]
local p = tonumber(args.precision) or 0
if (hex) then
local rgb = hexToRgb(hex)
local LCh = srgbToCielchuvD65o2deg(rgb.r, rgb.g, rgb.b)
return string.format("%." .. p .. "f, %." .. p .. "f, ", LCh.L, LCh.C) .. formatAngle(LCh.h, p)
else
return ""
end
end
return p