Jump to content
Toggle menu
  • 8 articles
  • 75 files
  • 7 users
  • 37.8K edits
Neodyland Wiki
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Module:Color: Difference between revisions

From Neodyland Wiki
Created
 
m 17 revisions imported
 
(16 intermediate revisions by 5 users not shown)
Line 1: Line 1:
-- Introduction: https://colorspace.r-forge.r-project.org/articles/color_spaces.html
local p = {}
local p = {}
local function isempty(v)
return v == nil or v == ''
end


local function hexToRgb(color)
local function hexToRgb(color)
if (#color == 6) then
local cleanColor = color:gsub("#", "#"):match('^[%s#]*(.-)[%s;]*$')
if (#cleanColor == 6) then
return {
return {
r = tonumber(string.sub(color, 1, 2), 16),
r = tonumber(string.sub(cleanColor, 1, 2), 16),
g = tonumber(string.sub(color, 3, 4), 16),
g = tonumber(string.sub(cleanColor, 3, 4), 16),
b = tonumber(string.sub(color, 5, 6), 16)
b = tonumber(string.sub(cleanColor, 5, 6), 16)
}
}
elseif (#color == 3) then
elseif (#cleanColor == 3) then
return {
return {
r = 17 * tonumber(string.sub(color, 1, 1), 16),
r = 17 * tonumber(string.sub(cleanColor, 1, 1), 16),
g = 17 * tonumber(string.sub(color, 2, 2), 16),
g = 17 * tonumber(string.sub(cleanColor, 2, 2), 16),
b = 17 * tonumber(string.sub(color, 3, 3), 16)
b = 17 * tonumber(string.sub(cleanColor, 3, 3), 16)
}
}
end
end
error("Invalid hexadecimal color " .. color, 1)
error("Invalid hexadecimal color " .. cleanColor, 1)
end
 
p._hexToRgb = hexToRgb
 
local function round(v)
if (v < 0) then
return math.ceil(v - 0.5)
else
return math.floor(v + 0.5)
end
end
end
local function rgbToHex(r, g, b)
return string.format("%02X%02X%02X", round(r), round(g), round(b))
end
p._rgbToHex = rgbToHex
local function rgbToCmyk(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
local c = 1 - r / 255
local m = 1 - g / 255
local y = 1 - b / 255
local k = math.min(c, m, y)
if (k == 1) then
c = 0
m = 0
y = 0
else
local d = 1 - k
c = (c - k) / d
m = (m - k) / d
y = (y - k) / d
end
return { c = c * 100, m = m * 100, y = y * 100, k = k * 100 }
end
p._rgbToCmyk = rgbToCmyk


local function rgbToHsl(r, g, b)
local function rgbToHsl(r, g, b)
Line 22: Line 68:
error("Color level out of bounds")
error("Color level out of bounds")
end
end
channelMax = math.max(r, g, b)
local channelMax = math.max(r, g, b)
channelMin = math.min(r, g, b)
local channelMin = math.min(r, g, b)
range = channelMax - channelMin
local range = channelMax - channelMin
local h, s
if (range == 0) then
if (range == 0) then
h = 0
h = 0
elseif (channelMax == r) then
elseif (channelMax == r) then
h = 60 * ((g - b) / range)
h = 60 * ((g - b) / range)
if (h < 0) then
h = 360 + h
end
elseif (channelMax == g) then
elseif (channelMax == g) then
h = 60 * (2 + (b - r) / range)
h = 60 * (2 + (b - r) / range)
Line 34: Line 84:
h = 60 * (4 + (r - g) / range)
h = 60 * (4 + (r - g) / range)
end
end
if (h < 0) then
local L = channelMax + channelMin
h = 360 + h
end
L = channelMax + channelMin
if (L == 0 or L == 510) then
if (L == 0 or L == 510) then
s = 0
s = 0
Line 45: Line 92:
return { h = h, s = s, l = L * 50 / 255 }
return { h = h, s = s, l = L * 50 / 255 }
end
end
p._rgbToHsl = rgbToHsl


local function rgbToHsv(r, g, b)
local function rgbToHsv(r, g, b)
Line 50: Line 99:
error("Color level out of bounds")
error("Color level out of bounds")
end
end
channelMax = math.max(r, g, b)
local channelMax = math.max(r, g, b)
channelMin = math.min(r, g, b)
local channelMin = math.min(r, g, b)
range = channelMax - channelMin
local range = channelMax - channelMin
local h, s
if (range == 0) then
if (range == 0) then
h = 0
h = 0
elseif (channelMax == r) then
elseif (channelMax == r) then
h = 60 * ((g - b) / range)
h = 60 * ((g - b) / range)
if (h < 0) then
h = 360 + h
end
elseif (channelMax == g) then
elseif (channelMax == g) then
h = 60 * (2 + (b - r) / range)
h = 60 * (2 + (b - r) / range)
else
else
h = 60 * (4 + (r - g) / range)
h = 60 * (4 + (r - g) / range)
end
if (h < 0) then
h = 360 + h
end
end
if (channelMax == 0) then
if (channelMax == 0) then
Line 72: Line 122:
return { h = h, s = s, v = channelMax * 100 / 255 }
return { h = h, s = s, v = channelMax * 100 / 255 }
end
end
p._rgbToHsv = rgbToHsv


-- c in [0, 255], condition tweaked for no discontinuity
-- c in [0, 255], condition tweaked for no discontinuity
-- http://entropymine.com/imageworsener/srgbformula/
-- http://entropymine.com/imageworsener/srgbformula/
local function inverseCompanding(c)
local function toLinear(c)
if (c > 10.314300250662591) then
if (c > 10.314300250662591) then
return math.pow((c + 14.025) / 269.025, 2.4)
return math.pow((c + 14.025) / 269.025, 2.4)
else
else
return c / 3294.6
return c / 3294.6
end
end
local function toNonLinear(c)
if (c > 0.00313066844250063) then
return 269.025 * math.pow(c, 1.0/2.4) - 14.025
else
return 3294.6 * c
end
end
end
end
Line 87: Line 147:
error("Color level out of bounds")
error("Color level out of bounds")
end
end
R = inverseCompanding(r)
local R = toLinear(r)
G = inverseCompanding(g)
local G = toLinear(g)
B = inverseCompanding(b)
local B = toLinear(b)
-- https://github.com/w3c/csswg-drafts/issues/5922
-- https://github.com/w3c/csswg-drafts/issues/5922
X = 0.1804807884018343 * B + 0.357584339383878 * G + 0.41239079926595934 * R
local X = 0.1804807884018343 * B + 0.357584339383878 * G + 0.41239079926595934 * R
Y = 0.07219231536073371 * B + 0.21263900587151027 * R + 0.715168678767756 * G
local Y = 0.07219231536073371 * B + 0.21263900587151027 * R + 0.715168678767756 * G
Z = 0.01933081871559182 * R + 0.11919477979462598 * G + 0.9505321522496607 * B
local Z = 0.01933081871559182 * R + 0.11919477979462598 * G + 0.9505321522496607 * B
local L, C, h
if (Y > 0.00885645167903563082) then
if (Y > 0.00885645167903563082) then
L = 116 * math.pow(Y, 1/3) - 16
L = 116 * math.pow(Y, 1/3) - 16
Line 99: Line 160:
L = Y * 903.2962962962962962963
L = Y * 903.2962962962962962963
end
end
if (r == g and g == b) then
if ((r == g and g == b) or L == 0) then
C = 0
C = 0
h = 0
h = 0
Line 110: Line 171:
-- 0.19783... and 0.4631... computed with extra precision from (X,Y,Z) when (R,G,B) = (1,1,1),
-- 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)
-- in which case (u,v) ≈ (0,0)
u = 13 * L * (4 * X / d - 0.19783000664283678994)
local us = 4 * X / d - 0.19783000664283678994
v = 13 * L * (9 * Y / d - 0.46831999493879099801)
local vs = 9 * Y / d - 0.46831999493879099801
h = math.atan2(v, u) * 57.2957795130823208768
h = math.atan2(vs, us) * 57.2957795130823208768
if (h < 0) then
if (h < 0) then
h = h + 360
h = h + 360
Line 118: Line 179:
h = 0 -- ensure zero is positive
h = 0 -- ensure zero is positive
end
end
C = math.sqrt(u * u + v * v)
C = math.sqrt(us * us + vs * vs) * 13 * L
if (C == 0) then
if (C == 0) then
C = 0
C = 0
Line 128: Line 189:
end
end


local function formatAngle(value, p)
p._srgbToCielchuvD65o2deg = srgbToCielchuvD65o2deg
local vs = string.format("%." .. p .. "f", value)
 
local zeros
local function srgbMix(t, r0, g0, b0, r1, g1, b1)
if (t > 1 or t < 0) then
error("Interpolation parameter out of bounds")
end
if (r0 > 255 or g0 > 255 or b0 > 255 or r1 > 255 or g1 > 255 or b1 > 255 or r0 < 0 or g0 < 0 or b0 < 0 or r1 < 0 or g1 < 0 or b1 < 0) then
error("Color level out of bounds")
end
local tc = 1 - t
return {
r = toNonLinear(tc * toLinear(r0) + t * toLinear(r1)),
g = toNonLinear(tc * toLinear(g0) + t * toLinear(g1)),
b = toNonLinear(tc * toLinear(b0) + t * toLinear(b1))
}
end
 
local function formatToPrecision(value, p)
return string.format("%." .. p .. "f", value)
end
 
local function getFractionalZeros(p)
if (p > 0) then
if (p > 0) then
zeros = "." .. string.rep("0", p)
return "." .. string.rep("0", p)
else
return ""
end
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
else
zeros = ""
return ""
end
end
if (vs == "360" .. zeros) then
end
vs = "0" .. zeros -- handle rounding to 360
 
function p.rgbTripletToHex(frame)
local args = frame.args or frame:getParent().args
local r = tonumber(args[1])
local g = tonumber(args[2])
local b = tonumber(args [3])
if (isempty(r) or isempty(g) or isempty(b)) then
return ""
else
return rgbToHex(r,g,b)
end
end
return vs .. "°"
end
end


function p.rgbhexToRgb(frame)
function p.hexToCmyk(frame)
local args = frame.args or frame:getParent().args
local args = frame.args or frame:getParent().args
local hex = args[1]
local hex = args[1]
if (hex) then
if (hex) then
local p = tonumber(args.precision) or 0
local s = args.pctsign or "1"
local rgb = hexToRgb(hex)
local rgb = hexToRgb(hex)
return rgb.r .. ', ' .. rgb.g .. ', ' .. rgb.b
local cmyk = rgbToCmyk(rgb.r, rgb.g, rgb.b)
local fk = formatToPrecision(cmyk.k, p)
local fc, fm, fy
local fracZeros = getFractionalZeros(p)
if (fk == 100  .. fracZeros) then
local fZero = 0 .. fracZeros
fc = fZero
fm = fZero
fy = fZero
else
fc = formatToPrecision(cmyk.c, p)
fm = formatToPrecision(cmyk.m, p)
fy = formatToPrecision(cmyk.y, p)
end
if (s ~= "0") then
return fc .. "%, " .. fm .. "%, " .. fy .. "%, " .. fk .. "%"
else
return fc .. ", " .. fm .. ", " .. fy .. ", " .. fk
end
else
else
return ""
return ""
Line 153: Line 272:
end
end


function p.rgbhexToHsl(frame)
function p.hexToHsl(frame)
local args = frame.args or frame:getParent().args
local args = frame.args or frame:getParent().args
local hex = args[1]
local hex = args[1]
local p = tonumber(args.precision) or 0
if (hex) then
if (hex) then
local p = tonumber(args.precision) or 0
local rgb = hexToRgb(hex)
local rgb = hexToRgb(hex)
local hsl = rgbToHsl(rgb.r, rgb.g, rgb.b)
local hsl = rgbToHsl(rgb.r, rgb.g, rgb.b)
return formatAngle(hsl.h, p) .. string.format(", %." .. p .. "f%%, %." .. p .. "f%%", hsl.s, hsl.l)
local fl = formatToPrecision(hsl.l, p)
local fs, fh
local fracZeros = getFractionalZeros(p)
local fZero = 0 .. fracZeros
if (fl == fZero or fl == 100 .. fracZeros) then
fs = fZero
fh = fZero
else
fs = formatToPrecision(hsl.s, p)
if (fs == fZero) then
fh = fZero
else
fh = formatToPrecision(hsl.h, p)
if (fh == 360 .. fracZeros) then
fh = fZero -- handle rounding to 360
end
end
end
return fh .. "°, " .. fs .. "%, " .. fl .. "%"
else
else
return ""
return ""
Line 166: Line 303:
end
end


function p.rgbhexToHsv(frame)
function p.hexToHsv(frame)
local args = frame.args or frame:getParent().args
local args = frame.args or frame:getParent().args
local hex = args[1]
local hex = args[1]
local p = tonumber(args.precision) or 0
if (hex) then
if (hex) then
local p = tonumber(args.precision) or 0
local rgb = hexToRgb(hex)
local rgb = hexToRgb(hex)
local hsv = rgbToHsv(rgb.r, rgb.g, rgb.b)
local hsv = rgbToHsv(rgb.r, rgb.g, rgb.b)
return formatAngle(hsv.h, p) .. string.format(", %." .. p .. "f%%, %." .. p .. "f%%", hsv.s, hsv.v)
local fv = formatToPrecision(hsv.v, p)
local fs, fh
local fracZeros = getFractionalZeros(p)
local fZero = 0 .. fracZeros
if (fv == fZero) then
fh = fZero
fs = fZero
else
fs = formatToPrecision(hsv.s, p)
if (fs == fZero) then
fh = fZero
else
fh = formatToPrecision(hsv.h, p)
if (fh == 360 .. fracZeros) then
fh = fZero -- handle rounding to 360
end
end
end
return fh .. "°, " .. fs .. "%, " .. fv .. "%"
else
else
return ""
return ""
Line 179: Line 334:
end
end


function p.rgbhexToCielch(frame)
function p.hexToCielch(frame)
local args = frame.args or frame:getParent().args
local args = frame.args or frame:getParent().args
local hex = args[1]
local hex = args[1]
local p = tonumber(args.precision) or 0
if (hex) then
if (hex) then
local p = tonumber(args.precision) or 0
local rgb = hexToRgb(hex)
local rgb = hexToRgb(hex)
local LCh = srgbToCielchuvD65o2deg(rgb.r, rgb.g, rgb.b)
local LCh = srgbToCielchuvD65o2deg(rgb.r, rgb.g, rgb.b)
return string.format("%." .. p .. "f, %." .. p .. "f, ", LCh.L, LCh.C) .. formatAngle(LCh.h, p)
local fL = formatToPrecision(LCh.L, p)
local fC, fh
local fracZeros = getFractionalZeros(p)
local fZero = 0 .. fracZeros
if (fL == fZero or fL == 100 .. fracZeros) then
fC = fZero
fh = fZero
else
fC = formatToPrecision(LCh.C, p)
if (fC == fZero) then
fh = fZero
else
fh = formatToPrecision(LCh.h, p)
if (fh == 360 .. fracZeros) then
fh = fZero -- handle rounding to 360
end
end
end
return fL .. ", " .. fC .. ", " .. fh .. "°"
else
else
return ""
return ""
end
end
end
function p.hexMix(frame)
local args = frame.args or frame:getParent().args
local hex0 = args[1]
local hex1 = args[2]
if (isempty(hex0) or isempty(hex1)) then
return ""
end
local t = tonumber(args[3])
if not t then
t = 0.5
else
local min = tonumber(args.min) or 0
local max = tonumber(args.max) or 100
if (min >= max) then
error("Minimum proportion greater than or equal to maximum")
elseif (t < min) then
t = 0
elseif (t > max) then
t = 1
else
t = (t - min) / (max - min)
end
end
local rgb0 = hexToRgb(hex0)
local rgb1 = hexToRgb(hex1)
local rgb = srgbMix(t, rgb0.r, rgb0.g, rgb0.b, rgb1.r, rgb1.g, rgb1.b)
return rgbToHex(rgb.r, rgb.g, rgb.b)
end
end


return p
return p

Latest revision as of 16:37, 12 August 2026

Module documentation[ view · edit · history · purge ]
This documentation is transcluded from Module:Color/doc.

This template is being used in the wrong namespace. Use {{Heavily used module}} instead! This module is used primarily by {{Infobox color}}, eliminating the need for external color converters and preventing mismatch between color coordinates.

Usage

edit

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 to 0 (zero)
  • pctsign: set to 0 (zero) to suppress percent signs in the generated output
  • proportion: proportion of color2, defaults to 50
  • min: minimum value of proportion range, defaults to 0
  • max: maximum value of proportion range, defaults to 100
-- Introduction: https://colorspace.r-forge.r-project.org/articles/color_spaces.html

local p = {}

local function isempty(v)
	return v == nil or v == ''
end

local function hexToRgb(color)
	local cleanColor = color:gsub("&#35;", "#"):match('^[%s#]*(.-)[%s;]*$')
	if (#cleanColor == 6) then
		return {
			r = tonumber(string.sub(cleanColor, 1, 2), 16),
			g = tonumber(string.sub(cleanColor, 3, 4), 16),
			b = tonumber(string.sub(cleanColor, 5, 6), 16)
		}
	elseif (#cleanColor == 3) then
		return {
			r = 17 * tonumber(string.sub(cleanColor, 1, 1), 16),
			g = 17 * tonumber(string.sub(cleanColor, 2, 2), 16),
			b = 17 * tonumber(string.sub(cleanColor, 3, 3), 16)
		}
	end
	error("Invalid hexadecimal color " .. cleanColor, 1)
end

p._hexToRgb = hexToRgb

local function round(v)
	if (v < 0) then
		return math.ceil(v - 0.5)
	else
		return math.floor(v + 0.5)
	end
end

local function rgbToHex(r, g, b)
	return string.format("%02X%02X%02X", round(r), round(g), round(b))
end

p._rgbToHex = rgbToHex

local function rgbToCmyk(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
	local c = 1 - r / 255
	local m = 1 - g / 255
	local y = 1 - b / 255
	local k = math.min(c, m, y)
	if (k == 1) then
		c = 0
		m = 0
		y = 0
	else
		local d = 1 - k
		c = (c - k) / d
		m = (m - k) / d
		y = (y - k) / d
	end
	return { c = c * 100, m = m * 100, y = y * 100, k = k * 100 }
end

p._rgbToCmyk = rgbToCmyk

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
	local channelMax = math.max(r, g, b)
	local channelMin = math.min(r, g, b)
	local range = channelMax - channelMin
	local h, s
	if (range == 0) then
		h = 0
	elseif (channelMax == r) then
		h = 60 * ((g - b) / range)
		if (h < 0) then
			h = 360 + h
		end
	elseif (channelMax == g) then
		h = 60 * (2 + (b - r) / range)
	else
		h = 60 * (4 + (r - g) / range)
	end
	local 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

p._rgbToHsl = rgbToHsl

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
	local channelMax = math.max(r, g, b)
	local channelMin = math.min(r, g, b)
	local range = channelMax - channelMin
	local h, s
	if (range == 0) then
		h = 0
	elseif (channelMax == r) then
		h = 60 * ((g - b) / range)
		if (h < 0) then
			h = 360 + h
		end
	elseif (channelMax == g) then
		h = 60 * (2 + (b - r) / range)
	else
		h = 60 * (4 + (r - g) / range)
	end
	if (channelMax == 0) then
		s = 0
	else
		s = 100 * range / channelMax
	end
	return { h = h, s = s, v = channelMax * 100 / 255 }
end

p._rgbToHsv = rgbToHsv

-- c in [0, 255], condition tweaked for no discontinuity
-- http://entropymine.com/imageworsener/srgbformula/
local function toLinear(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 toNonLinear(c)
	if (c > 0.00313066844250063) then
		return 269.025 * math.pow(c, 1.0/2.4) - 14.025
	else
		return 3294.6 * c
	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
	local R = toLinear(r)
	local G = toLinear(g)
	local B = toLinear(b)
	-- https://github.com/w3c/csswg-drafts/issues/5922
	local X = 0.1804807884018343 * B + 0.357584339383878 * G + 0.41239079926595934 * R
	local Y = 0.07219231536073371 * B + 0.21263900587151027 * R + 0.715168678767756 * G
	local Z = 0.01933081871559182 * R + 0.11919477979462598 * G + 0.9505321522496607 * B
	local L, C, h
	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) or L == 0) 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)
			local us = 4 * X / d - 0.19783000664283678994
			local vs = 9 * Y / d - 0.46831999493879099801
			h = math.atan2(vs, us) * 57.2957795130823208768
			if (h < 0) then
				h = h + 360
			elseif (h == 0) then
				h = 0 -- ensure zero is positive
			end
			C = math.sqrt(us * us + vs * vs) * 13 * L
			if (C == 0) then
				C = 0
				h = 0
			end
		end
	end
	return { L = L, C = C, h = h }
end

p._srgbToCielchuvD65o2deg = srgbToCielchuvD65o2deg

local function srgbMix(t, r0, g0, b0, r1, g1, b1)
	if (t > 1 or t < 0) then
		error("Interpolation parameter out of bounds")
	end
	if (r0 > 255 or g0 > 255 or b0 > 255 or r1 > 255 or g1 > 255 or b1 > 255 or r0 < 0 or g0 < 0 or b0 < 0 or r1 < 0 or g1 < 0 or b1 < 0) then
		error("Color level out of bounds")
	end
	local tc = 1 - t
	return {
		r = toNonLinear(tc * toLinear(r0) + t * toLinear(r1)),
		g = toNonLinear(tc * toLinear(g0) + t * toLinear(g1)),
		b = toNonLinear(tc * toLinear(b0) + t * toLinear(b1))
	}
end

local function formatToPrecision(value, p)
	return string.format("%." .. p .. "f", value)
end

local function getFractionalZeros(p)
	if (p > 0) then
		return "." .. string.rep("0", p)
	else
		return ""
	end
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.rgbTripletToHex(frame)
	local args = frame.args or frame:getParent().args
	local r = tonumber(args[1])
	local g = tonumber(args[2])
	local b = tonumber(args [3])
	if (isempty(r) or isempty(g) or isempty(b)) then
		return ""
	else
		return rgbToHex(r,g,b)
	end
end

function p.hexToCmyk(frame)
	local args = frame.args or frame:getParent().args
	local hex = args[1]
	if (hex) then
		local p = tonumber(args.precision) or 0
		local s = args.pctsign or "1"
		local rgb = hexToRgb(hex)
		local cmyk = rgbToCmyk(rgb.r, rgb.g, rgb.b)
		local fk = formatToPrecision(cmyk.k, p)
		local fc, fm, fy
		local fracZeros = getFractionalZeros(p)
		if (fk == 100  .. fracZeros) then
			local fZero = 0 .. fracZeros
			fc = fZero
			fm = fZero
			fy = fZero
		else
			fc = formatToPrecision(cmyk.c, p)
			fm = formatToPrecision(cmyk.m, p)
			fy = formatToPrecision(cmyk.y, p)
		end
		if (s ~= "0") then
			return fc .. "%, " .. fm .. "%, " .. fy .. "%, " .. fk .. "%"
		else
			return fc .. ", " .. fm .. ", " .. fy .. ", " .. fk
		end
	else
		return ""
	end
end

function p.hexToHsl(frame)
	local args = frame.args or frame:getParent().args
	local hex = args[1]
	if (hex) then
		local p = tonumber(args.precision) or 0
		local rgb = hexToRgb(hex)
		local hsl = rgbToHsl(rgb.r, rgb.g, rgb.b)
		local fl = formatToPrecision(hsl.l, p)
		local fs, fh
		local fracZeros = getFractionalZeros(p)
		local fZero = 0 .. fracZeros
		if (fl == fZero or fl == 100 .. fracZeros) then
			fs = fZero
			fh = fZero
		else
			fs = formatToPrecision(hsl.s, p)
			if (fs == fZero) then
				fh = fZero
			else
				fh = formatToPrecision(hsl.h, p)
				if (fh == 360 .. fracZeros) then
					fh = fZero -- handle rounding to 360
				end
			end
		end
		return fh .. "°, " .. fs .. "%, " .. fl .. "%"
	else
		return ""
	end
end

function p.hexToHsv(frame)
	local args = frame.args or frame:getParent().args
	local hex = args[1]
	if (hex) then
		local p = tonumber(args.precision) or 0
		local rgb = hexToRgb(hex)
		local hsv = rgbToHsv(rgb.r, rgb.g, rgb.b)
		local fv = formatToPrecision(hsv.v, p)
		local fs, fh
		local fracZeros = getFractionalZeros(p)
		local fZero = 0 .. fracZeros
		if (fv == fZero) then
			fh = fZero
			fs = fZero
		else
			fs = formatToPrecision(hsv.s, p)
			if (fs == fZero) then
				fh = fZero
			else
				fh = formatToPrecision(hsv.h, p)
				if (fh == 360 .. fracZeros) then
					fh = fZero -- handle rounding to 360
				end
			end
		end
		return fh .. "°, " .. fs .. "%, " .. fv .. "%"
	else
		return ""
	end
end

function p.hexToCielch(frame)
	local args = frame.args or frame:getParent().args
	local hex = args[1]
	if (hex) then
		local p = tonumber(args.precision) or 0
		local rgb = hexToRgb(hex)
		local LCh = srgbToCielchuvD65o2deg(rgb.r, rgb.g, rgb.b)
		local fL = formatToPrecision(LCh.L, p)
		local fC, fh
		local fracZeros = getFractionalZeros(p)
		local fZero = 0 .. fracZeros
		if (fL == fZero or fL == 100 .. fracZeros) then
			fC = fZero
			fh = fZero
		else
			fC = formatToPrecision(LCh.C, p)
			if (fC == fZero) then
				fh = fZero
			else
				fh = formatToPrecision(LCh.h, p)
				if (fh == 360 .. fracZeros) then
					fh = fZero -- handle rounding to 360
				end
			end
		end
		return fL .. ", " .. fC .. ", " .. fh .. "°"
	else
		return ""
	end
end

function p.hexMix(frame)
	local args = frame.args or frame:getParent().args
	local hex0 = args[1]
	local hex1 = args[2]
	if (isempty(hex0) or isempty(hex1)) then
		return ""
	end
	local t = tonumber(args[3])
	if not t then
		t = 0.5
	else
		local min = tonumber(args.min) or 0
		local max = tonumber(args.max) or 100
		if (min >= max) then
			error("Minimum proportion greater than or equal to maximum")
		elseif (t < min) then
			t = 0
		elseif (t > max) then
			t = 1
		else
			t = (t - min) / (max - min)
		end
	end
	local rgb0 = hexToRgb(hex0)
	local rgb1 = hexToRgb(hex1)
	local rgb = srgbMix(t, rgb0.r, rgb0.g, rgb0.b, rgb1.r, rgb1.g, rgb1.b)
	return rgbToHex(rgb.r, rgb.g, rgb.b)
end

return p
Contents
Cookies help us deliver our services. By using our services, you agree to our use of cookies.