Module:Color and Module:Color/sandbox: Difference between pages
Appearance
(Difference between pages)
Content deleted Content added
protect against junk value |
|||
Line 3: | Line 3: | ||
local p = {} |
local p = {} |
||
local function |
local function isEmpty(value) |
||
return |
return value == nil or value == '' |
||
end |
end |
||
local function |
local function isNotEmpty(value) |
||
return value ~= nil and value ~= '' |
|||
local cleanColor = color:gsub("#", "#"):match('^[%s#]*(.-)[%s;]*$') |
|||
end |
|||
local function argDefault(value, default) |
|||
if (value == nil or value == '') then |
|||
return default |
|||
else |
|||
return value |
|||
end |
|||
end |
|||
local function numArgDefault(value, default) |
|||
if (value == nil or value == '') then |
|||
return default |
|||
else |
|||
return tonumber(value) |
|||
end |
|||
end |
|||
local function isArgTrue(value) |
|||
return (value ~= nil and value ~= '' and value ~= '0') |
|||
end |
|||
local function isEmpty(value) |
|||
return value == nil or value == '' |
|||
end |
|||
local function isNotEmpty(value) |
|||
return value ~= nil and value ~= '' |
|||
end |
|||
local function hexToRgb(hexColor) |
|||
local cleanColor = hexColor:gsub('#', '#'):match('^[%s#]*(.-)[%s;]*$') |
|||
if (#cleanColor == 6) then |
if (#cleanColor == 6) then |
||
return |
return |
||
tonumber(string.sub(cleanColor, 1, 2), 16), |
|||
tonumber(string.sub(cleanColor, 3, 4), 16), |
|||
tonumber(string.sub(cleanColor, 5, 6), 16) |
|||
} |
|||
elseif (#cleanColor == 3) then |
elseif (#cleanColor == 3) then |
||
return |
return |
||
17 * tonumber(string.sub(cleanColor, 1, 1), 16), |
|||
17 * tonumber(string.sub(cleanColor, 2, 2), 16), |
|||
17 * tonumber(string.sub(cleanColor, 3, 3), 16) |
|||
} |
|||
end |
end |
||
error( |
error('Invalid hexadecimal color ' .. cleanColor, 1) |
||
end |
end |
||
local function round( |
local function round(value) |
||
if ( |
if (value < 0) then |
||
return math.ceil( |
return math.ceil(value - 0.5) |
||
else |
else |
||
return math.floor( |
return math.floor(value + 0.5) |
||
end |
end |
||
end |
end |
||
local function rgbToHex(r, g, b) |
local function rgbToHex(r, g, b) |
||
return string.format( |
return string.format('%02X%02X%02X', round(r), round(g), round(b)) |
||
end |
end |
||
local function |
local function checkRgb(r, g, b) |
||
if (r > 255 or g > 255 or b > 255 or r < 0 or g < 0 or b < 0) then |
if (r > 255 or g > 255 or b > 255 or r < 0 or g < 0 or b < 0) then |
||
error( |
error('Color level out of bounds') |
||
end |
end |
||
end |
|||
local function rgbToCmyk(r, g, b) |
|||
local c = 1 - r / 255 |
local c = 1 - r / 255 |
||
local m = 1 - g / 255 |
local m = 1 - g / 255 |
||
Line 50: | Line 83: | ||
y = 0 |
y = 0 |
||
else |
else |
||
local |
local kc = 1 - k |
||
c = (c - k) / |
c = (c - k) / kc |
||
m = (m - k) / |
m = (m - k) / kc |
||
y = (y - k) / |
y = (y - k) / kc |
||
end |
end |
||
return |
return c * 100, m * 100, y * 100, k * 100 |
||
end |
end |
||
local function rgbToHsl(r, g, b) |
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 channelMax = math.max(r, g, b) |
||
local channelMin = math.min(r, g, b) |
local channelMin = math.min(r, g, b) |
||
Line 84: | Line 114: | ||
s = 100 * range / math.min(L, 510 - L) |
s = 100 * range / math.min(L, 510 - L) |
||
end |
end |
||
return |
return h, s, L * 50 / 255 |
||
end |
end |
||
local function rgbToHsv(r, g, b) |
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 channelMax = math.max(r, g, b) |
||
local channelMin = math.min(r, g, b) |
local channelMin = math.min(r, g, b) |
||
Line 112: | Line 139: | ||
s = 100 * range / channelMax |
s = 100 * range / channelMax |
||
end |
end |
||
return |
return h, s, channelMax * 100 / 255 |
||
end |
|||
local function checkHsv(h, s, v) |
|||
if (s > 100 or v > 100 or s < 0 or v < 0) then |
|||
error('Color level out of bounds') |
|||
end |
|||
end |
|||
local function hsvToRgb(h, s, v) |
|||
local hn = (h / 60 - 6 * math.floor(h / 360)) |
|||
local hi = math.floor(hn) |
|||
local hr = hn - hi |
|||
local sn = s / 100 |
|||
local vs = v * 255 / 100 |
|||
local p = vs * (1 - sn); |
|||
local q = vs * (1 - sn * hr); |
|||
local t = vs * (1 - sn * (1 - hr)); |
|||
if (hi < 3) then |
|||
if (hi == 0) then |
|||
return vs, t, p |
|||
elseif (hi == 1) then |
|||
return q, vs, p |
|||
else |
|||
return p, vs, t |
|||
end |
|||
else |
|||
if (hi == 3) then |
|||
return p, q, vs |
|||
elseif (hi == 4) then |
|||
return t, p, vs |
|||
else |
|||
return vs, p, q |
|||
end |
|||
end |
|||
end |
end |
||
Line 134: | Line 195: | ||
local function srgbToCielchuvD65o2deg(r, g, b) |
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 R = toLinear(r) |
||
local G = toLinear(g) |
local G = toLinear(g) |
||
Line 176: | Line 234: | ||
end |
end |
||
end |
end |
||
return |
return L, C, h |
||
end |
end |
||
local function |
local function checkInterpolationParameter(t) |
||
if (t > 1 or t < 0) then |
if (t > 1 or t < 0) then |
||
error( |
error('Interpolation parameter out of bounds') |
||
end |
end |
||
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") |
|||
local function srgbMix(t, r0, g0, b0, r1, g1, b1) |
|||
local tc = 1 - t |
|||
return |
|||
toNonLinear(tc * toLinear(r0) + t * toLinear(r1)), |
|||
toNonLinear(tc * toLinear(g0) + t * toLinear(g1)), |
|||
toNonLinear(tc * toLinear(b0) + t * toLinear(b1)) |
|||
end |
|||
-- functions for generating gradients, inspired by OKLCH but not needing gamut mapping |
|||
local function adjustHueToCielch(h) |
|||
local n = 180 * math.floor(h / 180) |
|||
local d = h - n |
|||
if (d < 60) then |
|||
d = 73.7 * d / 60 |
|||
elseif (d < 120) then |
|||
d = 0.6975 * d + 31.85 |
|||
else |
|||
d = 1.07416666666666666667 * d - 13.35 |
|||
end |
|||
return n + d |
|||
end |
|||
local function unadjustHueFromCielch(h) |
|||
local n = 180 * math.floor(h / 180) |
|||
local d = h - n |
|||
if (d < 73.7) then |
|||
d = 0.81411126187245590231 * d |
|||
elseif (d < 115.55) then |
|||
d = 1.43369175627240143369 * d - 45.66308243727598566308 |
|||
else |
|||
d = 0.93095422808378588053 * d + 12.42823894491854150504 |
|||
end |
|||
return n + d |
|||
end |
|||
local function getLightness(r, g, b) |
|||
local Y = 0.07219231536073371 * toLinear(b) + 0.21263900587151027 * toLinear(r) + 0.715168678767756 * toLinear(g) |
|||
if (Y > 0.00885645167903563082) then |
|||
return 116 * math.pow(Y, 1/3) - 16 |
|||
else |
|||
return Y * 903.2962962962962962963 |
|||
end |
|||
end |
|||
local function adjustLightness(L, r, g, b) |
|||
if (L >= 100) then |
|||
return 255, 255, 255 |
|||
end |
|||
local Yc |
|||
if (L > 8) then |
|||
Yc = (L + 16) / 116 |
|||
Yc = Yc * Yc * Yc |
|||
else |
|||
Yc = L * 0.00110705645987945385 |
|||
end |
|||
local R = toLinear(r) |
|||
local G = toLinear(g) |
|||
local B = toLinear(b) |
|||
local Y = 0.07219231536073371 * B + 0.21263900587151027 * R + 0.715168678767756 * G |
|||
if (Y > 0) then |
|||
local scale = Yc / Y |
|||
R = R * scale |
|||
G = G * scale |
|||
B = B * scale |
|||
local cmax = math.max(R, G, B) |
|||
if (cmax > 1) then |
|||
R = R / cmax |
|||
G = G / cmax |
|||
B = B / cmax |
|||
local d = 0.07219231536073371 * (1 - B) + 0.21263900587151027 * (1 - R) + 0.715168678767756 * (1 - G) |
|||
if (d <= 0) then |
|||
R = 1 |
|||
G = 1 |
|||
B = 1 |
|||
else |
|||
local strength = 0.5 -- 1 yields equal lightness |
|||
local t = (Yc - 0.07219231536073371 * B - 0.21263900587151027 * R - 0.715168678767756 * G) / d |
|||
R = R + strength * (1 - R) * t |
|||
G = G + strength * (1 - G) * t |
|||
B = B + strength * (1 - B) * t |
|||
end |
|||
end |
|||
else |
|||
R = Yc |
|||
G = Yc |
|||
B = Yc |
|||
end |
|||
return toNonLinear(R), toNonLinear(G), toNonLinear(B) |
|||
end |
|||
local function interpolateHue(t, r0, g0, b0, r1, g1, b1, direction) |
|||
local h0, s0, v0 = rgbToHsv(r0, g0, b0) |
|||
local h1, s1, v1 = rgbToHsv(r1, g1, b1) |
|||
if (s0 == 0) then |
|||
h0 = h1 |
|||
if (v0 == 0) then |
|||
s0 = s1 |
|||
end |
|||
end |
|||
if (s1 == 0) then |
|||
h1 = h0 |
|||
if (v1 == 0) then |
|||
s1 = s1 |
|||
end |
|||
end |
|||
local hn0 = h0 / 360 |
|||
local hn1 = h1 / 360 |
|||
if (direction == 0) then |
|||
local dhn = hn1 - hn0 |
|||
if (dhn > 0.5) then |
|||
dhn = dhn - math.ceil(dhn - 0.5) |
|||
elseif (dhn < -0.5) then |
|||
dhn = dhn - math.floor(dhn + 0.5) |
|||
end |
|||
if (dhn >= 0) then |
|||
hn0 = hn0 - math.floor(hn0) |
|||
hn1 = hn0 + dhn |
|||
else |
|||
hn1 = hn1 - math.floor(hn1) |
|||
hn0 = hn1 - dhn |
|||
end |
|||
elseif (direction > 0) then |
|||
hn1 = 1 - math.ceil(hn1 - hn0) - math.floor(hn0) + hn1 |
|||
hn0 = hn0 - math.floor(hn0) |
|||
else |
|||
hn0 = 1 - math.ceil(hn0 - hn1) - math.floor(hn1) + hn0 |
|||
hn1 = hn1 - math.floor(hn1) |
|||
end |
|||
if (t < 0) then |
|||
t = 0 |
|||
elseif (t > 1) then |
|||
t = 1 |
|||
end |
end |
||
local tc = 1 - t |
local tc = 1 - t |
||
local ha = tc * adjustHueToCielch(360 * hn0) + t * adjustHueToCielch(360 * hn1) |
|||
return { |
|||
local r, g, b = hsvToRgb(unadjustHueFromCielch(ha), tc * s0 + t * s1, tc * v0 + t * v1) |
|||
r = toNonLinear(tc * toLinear(r0) + t * toLinear(r1)), |
|||
g = toNonLinear(tc * toLinear(g0) + t * toLinear(g1)), |
|||
local L0 = getLightness(r0, g0, b0) |
|||
b = toNonLinear(tc * toLinear(b0) + t * toLinear(b1)) |
|||
local L1 = getLightness(r1, g1, b1) |
|||
} |
|||
return adjustLightness(tc * L0 + t * L1, r, g, b) |
|||
end |
end |
||
local function formatToPrecision(value, p) |
local function formatToPrecision(value, p) |
||
return string.format( |
return string.format('%.' .. p .. 'f', value) |
||
end |
end |
||
local function getFractionalZeros(p) |
local function getFractionalZeros(p) |
||
if (p > 0) then |
if (p > 0) then |
||
return |
return '.' .. string.rep('0', p) |
||
else |
else |
||
return |
return '' |
||
end |
end |
||
end |
end |
||
function |
local function polyMix(t, palette) |
||
if (t <= 0) then |
|||
local args = frame.args or frame:getParent().args |
|||
return palette[1] |
|||
local hex = args[1] |
|||
elseif (t >= 1) then |
|||
return palette[#palette] |
|||
local rgb = hexToRgb(hex) |
|||
end |
|||
return rgb.r .. ', ' .. rgb.g .. ', ' .. rgb.b |
|||
local n, f = math.modf(t * (#palette - 1)) |
|||
if (f == 0) then |
|||
return palette[n + 1] |
|||
else |
else |
||
local r0, g0, b0 = hexToRgb(palette[n + 1]) |
|||
return "" |
|||
local r1, g1, b1 = hexToRgb(palette[n + 2]) |
|||
return rgbToHex(srgbMix(f, r0, g0, b0, r1, g1, b1)) |
|||
end |
end |
||
end |
end |
||
-- same principle: https://colorspace.r-forge.r-project.org/articles/hcl_palettes.html |
|||
function p.rgbTripletToHex(frame) |
|||
-- the darkest colors do not yield an WCAG AA contrast with text, maybe this can be solved by using HCL Wizard from R's Colorspace package |
|||
local args = frame.args or frame:getParent().args |
|||
-- https://colorspace.r-forge.r-project.org/articles/approximations.html |
|||
local r = tonumber(args[1]) |
|||
-- R's Colorspace does gamut mapping through simple clipping (as do most other color libraries, such as chroma.js and colorio), which is fast but not good |
|||
local g = tonumber(args[2]) |
|||
local function brewerGradient(t, palette) |
|||
local colors = { |
|||
if (isempty(r) or isempty(g) or isempty(b)) then |
|||
spectral = { '9E0142', 'D53E4F', 'F46D43', 'FDAE61', 'FEE08B', 'FFFFBF', 'E6F598', 'ABDDA4', '66C2A5', '3288BD', '5E4FA2' }, |
|||
return "" |
|||
rdylgn = { 'A50026', 'D73027', 'F46D43', 'FDAE61', 'FEE08B', 'FFFFBF', 'D9EF8B', 'A6D96A', '66BD63', '1A9850', '006837' }, |
|||
rdylbu = { 'A50026', 'D73027', 'F46D43', 'FDAE61', 'FEE090', 'FFFFBF', 'E0F3F8', 'ABD9E9', '74ADD1', '4575B4', '313695' }, |
|||
piyg = { '8E0152', 'C51B7D', 'DE77AE', 'F1B6DA', 'FDE0EF', 'F7F7F7', 'E6F5D0', 'B8E186', '7FBC41', '4D9221', '276419' }, |
|||
brbg = { '543005', '8C510A', 'BF812D', 'DFC27D', 'F6E8C3', 'F5F5F5', 'C7EAE5', '80CDC1', '35978F', '01665E', '003C30' }, |
|||
rdbu = { '67001F', 'B2182B', 'D6604D', 'F4A582', 'FDDBC7', 'F7F7F7', 'D1E5F0', '92C5DE', '4393C3', '2166AC', '053061' }, |
|||
prgn = { '40004B', '762A83', '9970AB', 'C2A5CF', 'E7D4E8', 'F7F7F7', 'D9F0D3', 'A6DBA0', '5AAE61', '1B7837', '00441B' }, |
|||
puor = { '7F3B08', 'B35806', 'E08214', 'FDB863', 'FEE0B6', 'F7F7F7', 'D8DAEB', 'B2ABD2', '8073AC', '542788', '2D004B' }, |
|||
rdgy = { '67001F', 'B2182B', 'D6604D', 'F4A582', 'FDDBC7', 'FFFFFF', 'E0E0E0', 'BABABA', '878787', '4D4D4D', '1A1A1A' }, |
|||
pubugn = { 'FFF7FB', 'ECE2F0', 'D0D1E6', 'A6BDDB', '67A9CF', '3690C0', '02818A', '016C59', '014636' }, |
|||
ylorrd = { 'FFFFCC', 'FFEDA0', 'FED976', 'FEB24C', 'FD8D3C', 'FC4E2A', 'E31A1C', 'BD0026', '800026' }, |
|||
ylorbr = { 'FFFFE5', 'FFF7BC', 'FEE391', 'FEC44F', 'FE9929', 'EC7014', 'CC4C02', '993404', '662506' }, |
|||
ylgnbu = { 'FFFFD9', 'EDF8B1', 'C7E9B4', '7FCDBB', '41B6C4', '1D91C0', '225EA8', '253494', '081D58' }, |
|||
gnbu = { 'F7FCF0', 'E0F3DB', 'CCEBC5', 'A8DDB5', '7BCCC4', '4EB3D3', '2B8CBE', '0868AC', '084081' }, |
|||
orrd = { 'FFF7EC', 'FEE8C8', 'FDD49E', 'FDBB84', 'FC8D59', 'EF6548', 'D7301F', 'B30000', '7F0000' }, |
|||
ylgn = { 'FFFFE5', 'F7FCB9', 'D9F0A3', 'ADDD8E', '78C679', '41AB5D', '238443', '006837', '004529' }, |
|||
bugn = { 'F7FCFD', 'E5F5F9', 'CCECE6', '99D8C9', '66C2A4', '41AE76', '238B45', '006D2C', '00441B' }, |
|||
pubu = { 'FFF7FB', 'ECE7F2', 'D0D1E6', 'A6BDDB', '74A9CF', '3690C0', '0570B0', '045A8D', '023858' }, |
|||
purd = { 'F7F4F9', 'E7E1EF', 'D4B9DA', 'C994C7', 'DF65B0', 'E7298A', 'CE1256', '980043', '67001F' }, |
|||
rdpu = { 'FFF7F3', 'FDE0DD', 'FCC5C0', 'FA9FB5', 'F768A1', 'DD3497', 'AE017E', '7A0177', '49006A' }, |
|||
bupu = { 'F7FCFD', 'E0ECF4', 'BFD3E6', '9EBCDA', '8C96C6', '8C6BB1', '88419D', '810F7C', '4D004B' }, |
|||
oranges = { 'FFF5EB', 'FEE6CE', 'FDD0A2', 'FDAE6B', 'FD8D3C', 'F16913', 'D94801', 'A63603', '7F2704' }, |
|||
greens = { 'F7FCF5', 'E5F5E0', 'C7E9C0', 'A1D99B', '74C476', '41AB5D', '238B45', '006D2C', '00441B' }, |
|||
blues = { 'F7FBFF', 'DEEBF7', 'C6DBEF', '9ECAE1', '6BAED6', '4292C6', '2171B5', '08519C', '08306B' }, |
|||
reds = { 'FFF5F0', 'FEE0D2', 'FCBBA1', 'FC9272', 'FB6A4A', 'EF3B2C', 'CB181D', 'A50F15', '67000D' }, |
|||
purples = { 'FCFBFD', 'EFEDF5', 'DADAEB', 'BCBDDC', '9E9AC8', '807DBA', '6A51A3', '54278F', '3F007D' }, |
|||
greys = { 'FFFFFF', 'F0F0F0', 'D9D9D9', 'BDBDBD', '969696', '737373', '525252', '252525', '000000' } |
|||
} |
|||
return polyMix(t, colors[palette]) |
|||
end |
|||
local function softSigmoid(x) |
|||
local ax = math.abs(x) |
|||
if (ax > 0.000000000000000111) then |
|||
return x / (1 + ax) |
|||
else |
else |
||
return |
return x |
||
end |
|||
end |
|||
function p.hexToRgbTriplet(frame) |
|||
local args = frame.args or frame:getParent().args |
|||
local hex = args[1] |
|||
if (isEmpty(hex)) then |
|||
return '' |
|||
end |
end |
||
local r, g, b = hexToRgb(hex) |
|||
return r .. ', ' .. g .. ', ' .. b |
|||
end |
end |
||
Line 232: | Line 478: | ||
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 (isEmpty(hex)) then |
||
return '' |
|||
local p = tonumber(args.precision) or 0 |
|||
end |
|||
local s = args.pctsign or "1" |
|||
local p = numArgDefault(args.precision, 0) |
|||
local s = args.pctsign or '1' |
|||
local c, m, y, k = rgbToCmyk(hexToRgb(hex)) |
|||
local fk = formatToPrecision(cmyk.k, p) |
|||
local fk = formatToPrecision(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 |
||
fc = formatToPrecision(c, p) |
|||
return "" |
|||
fm = formatToPrecision(m, p) |
|||
fy = formatToPrecision(y, p) |
|||
end |
|||
if (s ~= '0') then |
|||
return fc .. '%, ' .. fm .. '%, ' .. fy .. '%, ' .. fk .. '%' |
|||
else |
|||
return fc .. ', ' .. fm .. ', ' .. fy .. ', ' .. fk |
|||
end |
end |
||
end |
end |
||
Line 263: | Line 507: | ||
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 (isEmpty(hex)) then |
||
return '' |
|||
local p = tonumber(args.precision) or 0 |
|||
end |
|||
local rgb = hexToRgb(hex) |
|||
local p = numArgDefault(args.precision, 0) |
|||
local h, s, l = rgbToHsl(hexToRgb(hex)) |
|||
local fl = formatToPrecision(hsl.l, p) |
|||
local fl = formatToPrecision(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(s, p) |
|||
if (fs == fZero) then |
|||
fh = fZero |
fh = fZero |
||
else |
else |
||
fh = formatToPrecision(h, p) |
|||
if ( |
if (fh == 360 .. fracZeros) then |
||
fh = fZero |
fh = fZero -- handle rounding to 360 |
||
else |
|||
fh = formatToPrecision(hsl.h, p) |
|||
if (fh == 360 .. fracZeros) then |
|||
fh = fZero -- handle rounding to 360 |
|||
end |
|||
end |
end |
||
end |
end |
||
return fh .. "°, " .. fs .. "%, " .. fl .. "%" |
|||
else |
|||
return "" |
|||
end |
end |
||
return fh .. '°, ' .. fs .. '%, ' .. fl .. '%' |
|||
end |
end |
||
Line 294: | Line 536: | ||
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 (isEmpty(hex)) then |
||
return '' |
|||
local p = tonumber(args.precision) or 0 |
|||
end |
|||
local rgb = hexToRgb(hex) |
|||
local p = numArgDefault(args.precision, 0) |
|||
local h, s, v = rgbToHsv(hexToRgb(hex)) |
|||
local fv = formatToPrecision(hsv.v, p) |
|||
local fv = formatToPrecision(v, p) |
|||
local fs, fh |
|||
local fracZeros = getFractionalZeros(p) |
|||
local fZero = 0 .. fracZeros |
|||
if (fv == fZero) then |
|||
fh = fZero |
|||
fs = fZero |
|||
else |
|||
fs = formatToPrecision(s, p) |
|||
if (fs == fZero) then |
|||
fh = fZero |
fh = fZero |
||
fs = fZero |
|||
else |
else |
||
fh = formatToPrecision(h, p) |
|||
if ( |
if (fh == 360 .. fracZeros) then |
||
fh = fZero |
fh = fZero -- handle rounding to 360 |
||
else |
|||
fh = formatToPrecision(hsv.h, p) |
|||
if (fh == 360 .. fracZeros) then |
|||
fh = fZero -- handle rounding to 360 |
|||
end |
|||
end |
end |
||
end |
end |
||
return fh .. "°, " .. fs .. "%, " .. fv .. "%" |
|||
else |
|||
return "" |
|||
end |
end |
||
return fh .. '°, ' .. fs .. '%, ' .. fv .. '%' |
|||
end |
end |
||
Line 325: | Line 565: | ||
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 (isEmpty(hex)) then |
||
return '' |
|||
local p = tonumber(args.precision) or 0 |
|||
end |
|||
local rgb = hexToRgb(hex) |
|||
local p = numArgDefault(args.precision, 0) |
|||
local L, C, h = srgbToCielchuvD65o2deg(hexToRgb(hex)) |
|||
local fL = formatToPrecision(LCh.L, p) |
|||
local fL = formatToPrecision(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(C, p) |
|||
if (fC == fZero) then |
|||
fh = fZero |
fh = fZero |
||
else |
else |
||
fh = formatToPrecision(h, p) |
|||
if ( |
if (fh == 360 .. fracZeros) then |
||
fh = fZero |
fh = fZero -- handle rounding to 360 |
||
else |
|||
fh = formatToPrecision(LCh.h, p) |
|||
if (fh == 360 .. fracZeros) then |
|||
fh = fZero -- handle rounding to 360 |
|||
end |
|||
end |
end |
||
end |
end |
||
return fL .. ", " .. fC .. ", " .. fh .. "°" |
|||
else |
|||
return "" |
|||
end |
end |
||
return fL .. ', ' .. fC .. ', ' .. fh .. '°' |
|||
end |
end |
||
Line 357: | Line 595: | ||
local hex0 = args[1] |
local hex0 = args[1] |
||
local hex1 = args[2] |
local hex1 = args[2] |
||
if ( |
if (isEmpty(hex0) or isEmpty(hex1)) then |
||
return |
return '' |
||
end |
end |
||
local t = |
local t = args[3] |
||
if |
if (isEmpty(t)) then |
||
t = 0.5 |
t = 0.5 |
||
else |
else |
||
t = tonumber(t) |
|||
local |
local amin = numArgDefault(args.min, 0) |
||
local amax = numArgDefault(args.max, 100) |
|||
if (amax == amin) then |
|||
error("Minimum proportion greater than or equal to maximum") |
|||
t = 0.5 |
|||
t = 0 |
|||
elseif (t > max) then |
|||
t = 1 |
|||
else |
else |
||
t = (t - |
t = (t - amin) / (amax - amin) |
||
if (t > 1) then |
|||
t = 1 |
|||
elseif (t < 0) then |
|||
t = 0 |
|||
end |
|||
end |
end |
||
end |
end |
||
local |
local r0, g0, b0 = hexToRgb(hex0) |
||
local |
local r1, g1, b1 = hexToRgb(hex1) |
||
return rgbToHex(srgbMix(t, r0, g0, b0, r1, g1, b1)) |
|||
end |
|||
return rgbToHex(rgb.r, rgb.g, rgb.b) |
|||
function p.hexInterpolate(frame) |
|||
local args = frame.args or frame:getParent().args |
|||
local hex0 = args[1] |
|||
local hex1 = args[2] |
|||
if (isEmpty(hex0)) then |
|||
return hex1 |
|||
elseif (isEmpty(hex1)) then |
|||
return hex0 |
|||
end |
|||
local t = args[3] |
|||
if (isEmpty(t)) then |
|||
t = 0.5 |
|||
else |
|||
t = tonumber(t) |
|||
local amin = numArgDefault(args.min, 0) |
|||
local amax = numArgDefault(args.max, 100) |
|||
if (amax == amin) then |
|||
t = 0.5 |
|||
else |
|||
t = (t - amin) / (amax - amin) |
|||
if (t > 1) then |
|||
t = 1 |
|||
elseif (t < 0) then |
|||
t = 0 |
|||
end |
|||
end |
|||
end |
|||
local direction = numArgDefault(args.direction, 0) |
|||
local r0, g0, b0 = hexToRgb(hex0) |
|||
local r1, g1, b1 = hexToRgb(hex1) |
|||
return rgbToHex(interpolateHue(t, r0, g0, b0, r1, g1, b1, direction)) |
|||
end |
|||
function p.hexBrewerGradient(frame) |
|||
local args = frame.args or frame:getParent().args |
|||
local pal = argDefault(args.pal, 'spectral'):lower() |
|||
local value = args[1] |
|||
local t |
|||
if (isEmpty(value)) then |
|||
t = 0.5 |
|||
else |
|||
value = tonumber(value) |
|||
local high = numArgDefault(args.high, 100) |
|||
local low = numArgDefault(args.low, -100) |
|||
if (isEmpty(args.low)) then |
|||
if (pal ~= 'spectral' and pal ~= 'rdylgn' and pal ~= 'rdylbu' and (pal:len() ~= 4 or |
|||
(pal ~= 'rdgy' and pal ~= 'rdbu' and pal ~= 'puor' and pal ~= 'prgn' and pal ~= 'piyg' and pal ~= 'brbg'))) then |
|||
low = 0 |
|||
end |
|||
end |
|||
if (high == low) then |
|||
t = 0.5 |
|||
elseif (isArgTrue(args.inv)) then |
|||
t = (high - value) / (high - low) |
|||
else |
|||
t = (value - low) / (high - low) |
|||
end |
|||
end |
|||
if (isArgTrue(args.comp)) then |
|||
t = 0.5 * softSigmoid(2 * t - 1) + 0.5 |
|||
end |
|||
return brewerGradient(t, pal) |
|||
end |
end |
||