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