Module:HctTool
此模块的文档可以在Module:HctTool/doc创建
local Hct = require('Module:Hct')
local colorUtils = require('Module:Hct/ColorUtils')
local mathUtils = require('Module:Hct/MathUtils')
local bit32 = require('bit32')
-- 全局变量转换为upvalue
local type = type
local tonumber = tonumber
local insert = table.insert
--[[- switch语法糖。
用法:
switch (值) {
[匹配] = 值或函数,
}
]]
local function switch(val)
return function(cases)
local hit = cases[val]
if type(hit) == 'function' then
return hit()
end
return hit
end
end
--- 分离'函数名(参数)'的函数名与参数。
local function separateFuncNameAndArg(str)
return str:match('^([a-z]+)%(%s*([^%(%)]+)%s*%)$')
end
local function hueToNumber(str)
return str == 'max' and 300 or tonumber(str)
end
--- 由argb得CSS <color>。
local function argbToString(argb, hashSign)
return string.format('%s%06X', hashSign or ' #', bit32.band(0xffffff, argb))
end
--- 从'#RRGGBB'之类的字符串得32位argb。
local function hexRgbToArgb(hexRgb)
-- 暂时忽略透明度
local rrggbb = switch (hexRgb:len()) {
[3] = function()
local r, g, b = hexRgb:match('^(.)(.)(.)')
return table.concat({r, r, g, g, b, b})
end,
[4] = function()
local r, g, b = hexRgb:match('^(.)(.)(.)')
return table.concat({r, r, g, g, b, b})
end,
[6] = hexRgb,
[8] = function()
return hexRgb:sub(1, 6)
end,
}
assert(rrggbb, '16进制RGB格式有误')
return bit32.bor(0xff000000, tonumber(rrggbb, 16))
end
--- 从诸如'#RRGGBB'、'rgb(r, g, b)'的字符串获取Hct对象。
local function hctFromString(str)
local PATTERNS = {
hex = '^#(%x+)$',
hct = '^([%+%-]?[%d%.]+)%s+([%+%-]?[%d%.max]+)%s+([%+%-]?[%d%.]+)$',
hct_comma = '^([%+%-]?[%d%.]+)%s*,%s*([%+%-]?[%d%.max]+)%s*,%s*([%+%-]?[%d%.]+)$',
rgb = '^(%d+)%s+(%d+)%s+(%d+)$',
rgb_legacy = '^(%d+)%s*,%s*(%d+)%s*,%s*(%d+)$'
}
local funcName, argStr = separateFuncNameAndArg(str)
if not funcName then
local h, c, t = str:match(PATTERNS.hct)
if not h then
h, c, t = str:match(PATTERNS.hct_comma)
end
if h then
return Hct.from(tonumber(h), hueToNumber(c), tonumber(t))
end
local hexRgb = str:match(PATTERNS.hex)
if hexRgb then
return Hct.fromInt(hexRgbToArgb(hexRgb))
end
error('不支持的颜色格式')
end
-- 颜色函数
local hct = switch (funcName) {
rgb = function ()
local r, g, b = argStr:match(PATTERNS.rgb)
if not r then
r, g, b = argStr:match(PATTERNS.rgb_legacy)
end
if r then
return Hct.fromInt(colorUtils.argbFromRgb(tonumber(r), tonumber(g), tonumber(b)))
end
error('rgb()参数有误')
end,
hct = function ()
local h, c, t = argStr:match(PATTERNS.hct)
if h then
return Hct.from(tonumber(h), hueToNumber(c), tonumber(t))
end
error('hct()参数有误')
end
}
if hct then return hct end
error('不支持的颜色格式')
end
--- 在HCT空间线性插值。
local function interpolate(hues, chromas, tones, totalCount, doesToString)
totalCount = totalCount
and math.max(#hues, #chromas, #tones, math.floor(totalCount))
or math.max(#hues, #chromas, #tones) * 2 - 1
local firstHct = Hct.from(
tonumber(hues[1]),
hueToNumber(chromas[1]),
tonumber(tones[1])
)
local lastHct = Hct.from(
tonumber(hues[#hues]),
hueToNumber(chromas[#chromas]),
tonumber(tones[#tones])
)
-- chroma max相关处理:
-- - max:当作300(一个绝对超过最大值的值)
-- - … ~ max / max ~ … / max ~ … ~ max:先计算max,再插值
-- - max只能出现在首尾
if #chromas == 1 then
if chromas[1] == 'max' then
chromas[1] = 300
end
else
if chromas[1] == 'max' then
chromas[1] = firstHct:getChroma()
end
if chromas[#chromas] == 'max' then
chromas[#chromas] = lastHct:getChroma()
end
end
local totalGapCount = totalCount - 1
local function valuesBetween(src)
local values = {}
local srcGapCount = #src - 1
for i = 1, totalGapCount - 1 do
local intg, frac = math.modf(i * srcGapCount / totalGapCount + 1)
if frac == 0 then
insert(values, tonumber(src[intg]))
else
insert(
values, mathUtils.lerp(tonumber(src[intg]), tonumber(src[intg + 1]), frac)
)
end
end
return values
end
hues, chromas, tones = valuesBetween(hues), valuesBetween(chromas), valuesBetween(tones)
local out = {}
if doesToString then
insert(out, argbToString(firstHct:toInt()))
for i = 1, totalGapCount - 1 do
insert(out, argbToString(Hct.from(hues[i], chromas[i], tones[i]):toInt()))
end
insert(out, argbToString(lastHct:toInt()))
else
insert(out, firstHct)
for i = 1, totalGapCount - 1 do
insert(out, Hct.from(hues[i], chromas[i], tones[i]))
end
insert(out, lastHct)
end
return out
end
--- 预处理frame.args。
local function processArgs(frameArgs)
local args = {}
local ops = {}
for k, v in pairs(frameArgs) do
local argIsntOperation = true
if type(k) == 'string' then
local attr, operator = k:match('^([hct])%s*([%+%-]?)$')
if attr then
argIsntOperation = false
vNumber = tonumber(v) or (k == 'c' and v == 'max' and 300)
assert(vNumber, k..'参数填写有误')
ops[attr] = { operator, vNumber }
end
else
v = v:match('^%s*(.-)%s*$')
end
if argIsntOperation and v ~= '' then
args[k] = v
end
end
-- 上面的循环排除了空字符串,但是参数'#'允许空字符串
args['#'] = frameArgs['#']
return args, ops
end
local p = {}
function p.main(frame)
local parent = frame:getParent()
if parent and parent:getTitle() == 'Template:Hct' then
frame = parent
end
return p._main(frame.args)
end
function p._main(frameArgs)
local args, ops = processArgs(frameArgs)
local hct
-- 分析匿名参数
if args[1] and args[2] and args[3] then
local hues = mw.text.split(args[1], '%s*~%s*')
local chromas = mw.text.split(args[2], '%s*~%s*')
local tones = mw.text.split(args[3], '%s*~%s*')
if #hues ~= 1 or #chromas ~= 1 or #tones ~= 1 then -- 线性插值语法
local colorListStr = table.concat(
interpolate(hues, chromas, tones, args.num, true), ','
)
return args['#'] and colorListStr:gsub('^ #', args['#'], 1) or colorListStr
end
hct = Hct.from(tonumber(args[1]), hueToNumber(args[2]), tonumber(args[3]))
elseif args[1] then
hct = hctFromString(args[1])
else
error('匿名参数填写有误')
end
-- 分量操作
if next(ops) then
local function newVal(oldVal, op)
if not op then return oldVal end
if op[1] == '+' then return oldVal + op[2] end
if op[1] == '-' then return oldVal - op[2] end
return op[2]
end
hct = Hct.from(
newVal(hct:getHue(), ops.h),
newVal(hct:getChroma(), ops.c),
newVal(hct:getTone(), ops.t)
)
end
-- 获取分量
if args.get then
local function serialize(num)
return (string.format('%.3f', num):gsub('%.?0+$', '', 1))
end
if args.get == 'h' then return serialize(hct:getHue()) end
if args.get == 'c' then return serialize(hct:getChroma()) end
if args.get == 't' then return serialize(hct:getTone()) end
if args.get == 'hct' then
return (string.format(
'hct(%.3f %.3f %.3f)',
hct:getHue(),
hct:getChroma(),
hct:getTone()):gsub('%.?0+%f[,%)]', '')
)
end
error('get参数值必须是“h”“c”“t”“hct”之一')
end
-- 默认是返回该HCT的颜色值
return argbToString(hct:toInt(), args['#'])
end
return p