มอดูล:Color

-- 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("#", "#"):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
local function round(v)
	if (v  255 or g > 255 or b > 255 or r  255 or g > 255 or b > 255 or r  255 or g > 255 or b > 255 or r  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.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  1 or t  255 or g0 > 255 or b0 > 255 or r1 > 255 or g1 > 255 or b1 > 255 or r0  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 = args1
	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(args1)
	local g = tonumber(args2)
	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 = args1
	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 = args1
	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 = args1
	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 = args1
	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 = args1
	local hex1 = args2
	if (isempty(hex0) or isempty(hex1)) then
		return ""
	end
	local t = args3
	if (isempty(t)) then
		t = 0.5
	else
		t = tonumber(t)
		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  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