Module:Color
跳到导航
跳到搜索
- -- 该模块主要用于操作颜色。
- local colorKeywords = {
- aliceblue = { 240, 248, 255 },
- antiquewhite = { 250, 235, 215 },
- aqua = { 0, 255, 255 },
- aquamarine = { 127, 255, 212 },
- azure = { 240, 255, 255 },
- beige = { 245, 245, 220 },
- bisque = { 255, 228, 196 },
- black = { 0, 0, 0 },
- blanchedalmond = { 255, 235, 205 },
- blue = { 0, 0, 255 },
- blueviolet = { 138, 43, 226 },
- brown = { 165, 42, 42 },
- burlywood = { 222, 184, 135 },
- cadetblue = { 95, 158, 160 },
- chartreuse = { 127, 255, 0 },
- chocolate = { 210, 105, 30 },
- coral = { 255, 127, 80 },
- cornflowerblue = { 100, 149, 237 },
- cornsilk = { 255, 248, 220 },
- crimson = { 220, 20, 60 },
- cyan = { 0, 255, 255 },
- darkblue = { 0, 0, 139 },
- darkcyan = { 0, 139, 139 },
- darkgoldenrod = { 184, 134, 11 },
- darkgray = { 169, 169, 169 },
- darkgreen = { 0, 100, 0 },
- darkgrey = { 169, 169, 169 },
- darkkhaki = { 189, 183, 107 },
- darkmagenta = { 139, 0, 139 },
- darkolivegreen = { 85, 107, 47 },
- darkorange = { 255, 140, 0 },
- darkorchid = { 153, 50, 204 },
- darkred = { 139, 0, 0 },
- darksalmon = { 233, 150, 122 },
- darkseagreen = { 143, 188, 143 },
- darkslateblue = { 72, 61, 139 },
- darkslategray = { 47, 79, 79 },
- darkslategrey = { 47, 79, 79 },
- darkturquoise = { 0, 206, 209 },
- darkviolet = { 148, 0, 211 },
- deeppink = { 255, 20, 147 },
- deepskyblue = { 0, 191, 255 },
- dimgray = { 105, 105, 105 },
- dimgrey = { 105, 105, 105 },
- dodgerblue = { 30, 144, 255 },
- firebrick = { 178, 34, 34 },
- floralwhite = { 255, 250, 240 },
- forestgreen = { 34, 139, 34 },
- fuchsia = { 255, 0, 255 },
- gainsboro = { 220, 220, 220 },
- ghostwhite = { 248, 248, 255 },
- gold = { 255, 215, 0 },
- goldenrod = { 218, 165, 32 },
- gray = { 128, 128, 128 },
- green = { 0, 128, 0 },
- greenyellow = { 173, 255, 47 },
- grey = { 128, 128, 128 },
- honeydew = { 240, 255, 240 },
- hotpink = { 255, 105, 180 },
- indianred = { 205, 92, 92 },
- indigo = { 75, 0, 130 },
- ivory = { 255, 255, 240 },
- khaki = { 240, 230, 140 },
- lavender = { 230, 230, 250 },
- lavenderblush = { 255, 240, 245 },
- lawngreen = { 124, 252, 0 },
- lemonchiffon = { 255, 250, 205 },
- lightblue = { 173, 216, 230 },
- lightcoral = { 240, 128, 128 },
- lightcyan = { 224, 255, 255 },
- lightgoldenrodyellow = { 250, 250, 210 },
- lightgray = { 211, 211, 211 },
- lightgreen = { 144, 238, 144 },
- lightgrey = { 211, 211, 211 },
- lightpink = { 255, 182, 193 },
- lightsalmon = { 255, 160, 122 },
- lightseagreen = { 32, 178, 170 },
- lightskyblue = { 135, 206, 250 },
- lightslategray = { 119, 136, 153 },
- lightslategrey = { 119, 136, 153 },
- lightsteelblue = { 176, 196, 222 },
- lightyellow = { 255, 255, 224 },
- lime = { 0, 255, 0 },
- limegreen = { 50, 205, 50 },
- linen = { 250, 240, 230 },
- magenta = { 255, 0, 255 },
- maroon = { 128, 0, 0 },
- mediumaquamarine = { 102, 205, 170 },
- mediumblue = { 0, 0, 205 },
- mediumorchid = { 186, 85, 211 },
- mediumpurple = { 147, 112, 219 },
- mediumseagreen = { 60, 179, 113 },
- mediumslateblue = { 123, 104, 238 },
- mediumspringgreen = { 0, 250, 154 },
- mediumturquoise = { 72, 209, 204 },
- mediumvioletred = { 199, 21, 133 },
- midnightblue = { 25, 25, 112 },
- mintcream = { 245, 255, 250 },
- mistyrose = { 255, 228, 225 },
- moccasin = { 255, 228, 181 },
- navajowhite = { 255, 222, 173 },
- navy = { 0, 0, 128 },
- oldlace = { 253, 245, 230 },
- olive = { 128, 128, 0 },
- olivedrab = { 107, 142, 35 },
- orange = { 255, 165, 0 },
- orangered = { 255, 69, 0 },
- orchid = { 218, 112, 214 },
- palegoldenrod = { 238, 232, 170 },
- palegreen = { 152, 251, 152 },
- paleturquoise = { 175, 238, 238 },
- palevioletred = { 219, 112, 147 },
- papayawhip = { 255, 239, 213 },
- peachpuff = { 255, 218, 185 },
- peru = { 205, 133, 63 },
- pink = { 255, 192, 203 },
- plum = { 221, 160, 221 },
- powderblue = { 176, 224, 230 },
- purple = { 128, 0, 128 },
- red = { 255, 0, 0 },
- rosybrown = { 188, 143, 143 },
- royalblue = { 65, 105, 225 },
- saddlebrown = { 139, 69, 19 },
- salmon = { 250, 128, 114 },
- sandybrown = { 244, 164, 96 },
- seagreen = { 46, 139, 87 },
- seashell = { 255, 245, 238 },
- sienna = { 160, 82, 45 },
- silver = { 192, 192, 192 },
- skyblue = { 135, 206, 235 },
- slateblue = { 106, 90, 205 },
- slategray = { 112, 128, 144 },
- slategrey = { 112, 128, 144 },
- snow = { 255, 250, 250 },
- springgreen = { 0, 255, 127 },
- steelblue = { 70, 130, 180 },
- tan = { 210, 180, 140 },
- teal = { 0, 128, 128 },
- thistle = { 216, 191, 216 },
- tomato = { 255, 99, 71 },
- turquoise = { 64, 224, 208 },
- violet = { 238, 130, 238 },
- wheat = { 245, 222, 179 },
- white = { 255, 255, 255 },
- whitesmoke = { 245, 245, 245 },
- yellow = { 255, 255, 0 },
- yellowgreen = { 154, 205, 50 },
- }
- local rgbRegex = '^rgb%(%s-(%d-),%s-(%d-)%s-,%s-(%d-)%s-%)$'
- local rgbaRegex = '^rgba%(%s-(%d-),%s-(%d-)%s-,%s-(%d-)%s-,%s-([%d%.]+)%s-%)$'
- local hslRegex = '^hsl%(%s-(%d-),%s-(%d-)%%%s-,%s-(%d-)%%%s-%)$'
- local hslaRegex = '^hsla%(%s-(%d-),%s-(%d-)%%%s-,%s-(%d-)%%%s-,%s-([%d%.]+)%s-%)$'
- local hexRegex = '^#(%x%x)(%x%x)(%x%x)$'
- local hexShorthandRegex = '^#(%x)(%x)(%x)$'
- --[[
- Color实例结构
- interface ColorInstance {
- __index = Color
- value: [number, number, number]
- format: 'rgb' | 'hsl'
- opacity: number
- }
- ]]
- local Color = {}
- local colorMetaTable = { __index = Color }
- --[[
- @param {number} min
- @param {number} max
- @return {number}
- ]]
- local function _random(min, max)
- return tonumber(mw.getCurrentFrame():expandTemplate{ title = 'random', args = { min, max } })
- end
- --[[
- @desc 操作颜色加深减淡
- @param {[number, number, number]} rgb
- @param {'+' | '-'} operator - 加深,减淡
- @param {number} ratio 范围:0 ~ 100
- @return {[number, number, number]}
- ]]
- local function _computeRgb(rgb, operator, ratio)
- local ranges = {}
- local cloneRgb = { rgb[1], rgb[2], rgb[3] }
- for i, v in ipairs(rgb) do
- ranges[i] = {
- ['-'] = (255 - v) / 100,
- ['+'] = -v / 100
- }
- end
- for i, v in ipairs(cloneRgb) do
- cloneRgb[i] = v + ranges[i][operator] * ratio
- if cloneRgb[i] < 0 then cloneRgb[i] = 0 end
- if cloneRgb[i] > 255 then cloneRgb[i] = 255 end
- end
- return cloneRgb
- end
- --[[
- @desc 判断一个字符串或table是否为合法的color值
- @param {(string | [number, number, number])} rawValue - 接受一个字符串或数组table,有效的格式有:css颜色关键字,hex颜色,hex简写颜色,rgb函数,rgba函数,hsl函数,hsla函数
- @return {boolean}
- ]]
- function Color.isColorStr(rawValue)
- if type(rawValue) == 'string' then
- if
- rawValue:match(rgbRegex) or
- rawValue:match(rgbaRegex) or
- rawValue:match(hslRegex) or
- rawValue:match(hslaRegex) or
- colorKeywords[rawValue]
- then return true end
- rawValue = mw.text.unstripNoWiki(rawValue)
- :gsub('^#', '#') -- 为了避免解析器自动换行,一些返回颜色值的模板常用'<nowiki>#</nowiki>'或'#'代替'#'
- :gsub('^#', '#') -- Bhsd加的全角字符兼容,不知道为啥
- if
- rawValue:match(hexRegex) or
- rawValue:match(hexShorthandRegex)
- then return true end
- elseif type(rawValue) == 'table' then
- if #rawValue ~= 3 and #rawValue ~= 4 then return false end
- for _, v in ipairs(rawValue) do
- if type(v) ~= 'number' then return false end
- end
- return true
- end
- return false
- end
- --[[
- @desc 创建一个Color实例
- @param {(string | [number, number, number])} rawValue - 接受一个字符串或数组table,有效的格式有:css颜色关键字,hex颜色,hex简写颜色,rgb函数,rgba函数,hsl函数,hsla函数
- @return {(Color | nil)} - 如果rawValue无效,则返回nil
- ]]
- function Color.create(rawValue)
- if not Color.isColorStr(rawValue) then return nil end
- if type(rawValue) == 'string' then
- rawValue = mw.text.unstripNoWiki(rawValue)
- :gsub('^#', '#') -- 为了避免解析器自动换行,一些返回颜色值的模板常用'<nowiki>#</nowiki>'或'#'代替'#'
- :gsub('^#', '#') -- Bhsd加的全角字符兼容,不知道为啥
- end
- local color = setmetatable({}, colorMetaTable)
- local r_h, g_s, b_l, opacity -- rgb or hsl
- if type(rawValue) == 'string' then
- if rawValue:match(rgbRegex) then
- color.format = 'rgb'
- r_h, g_s, b_l = rawValue:match(rgbRegex)
- r_h = tonumber(r_h)
- g_s = tonumber(g_s)
- b_l = tonumber(b_l)
- elseif rawValue:match(rgbaRegex) then
- color.format = 'rgb'
- r_h, g_s, b_l, opacity = rawValue:match(rgbaRegex)
- r_h = tonumber(r_h)
- g_s = tonumber(g_s)
- b_l = tonumber(b_l)
- opacity = tonumber(opacity)
- elseif rawValue:match(hslRegex) then
- color.format = 'hsl'
- r_h, g_s, b_l = rawValue:match(hslRegex)
- r_h = tonumber(r_h)
- g_s = tonumber(g_s)
- b_l = tonumber(b_l)
- elseif rawValue:match(hslaRegex) then
- color.format = 'hsl'
- r_h, g_s, b_l, opacity = rawValue:match(hslaRegex)
- r_h = tonumber(r_h)
- g_s = tonumber(g_s)
- b_l = tonumber(b_l)
- opacity = tonumber(opacity)
- elseif rawValue:match(hexRegex) then
- color.format = 'rgb'
- r_h, g_s, b_l = rawValue:match(hexRegex)
- r_h = tonumber(r_h, 16)
- g_s = tonumber(g_s, 16)
- b_l = tonumber(b_l, 16)
- elseif rawValue:match(hexShorthandRegex) then
- color.format = 'rgb'
- r_h, g_s, b_l = rawValue:match(hexShorthandRegex)
- r_h = tonumber(r_h, 16) * 17
- g_s = tonumber(g_s, 16) * 17
- b_l = tonumber(b_l, 16) * 17
- else
- color.format = 'rgb'
- local colorkeywordRgb = colorKeywords[rawValue]
- r_h = colorkeywordRgb[1]
- g_s = colorkeywordRgb[2]
- b_l = colorkeywordRgb[3]
- end
- elseif type(rawValue) == 'table' then
- color.format = 'rgb'
- r_h = rawValue[1]
- g_s = rawValue[2]
- b_l = rawValue[3]
- opacity = rawValue[4]
- end
- color.value = { r_h, g_s, b_l }
- color.opacity = opacity or 1
- return color
- end
- --[[
- @desc 克隆一个Color对象
- @param {Color} this
- @return {Color} - 一个新的Color对象
- ]]
- function Color.clone(this)
- local rgb = this:rgb().value
- return Color.create(rgb):setOpacity(this.opacity)
- end
- --[[
- @desc rgb转hsl
- @param {number} r
- @param {number} g
- @param {number} b
- @return [number, number, number] - 返回的所有值均为整数
- ]]
- function Color.rgb2hsl(r, g, b)
- r = r / 255
- g = g / 255
- b = b / 255
- local max = math.max(r, g, b)
- local min = math.min(r, g, b)
- local diff = max - min
- local h, s
- local l = (max + min) / 2
- if max == min then
- h = 0
- s = 0
- elseif max == r and g >= b then
- h = 60 * ((g - b) / diff)
- elseif max == r and g < b then
- h = 60 * ((g - b) / diff) + 360
- elseif max == g then
- h = 60 * ((b - r) / diff) + 120
- elseif max == b then
- h = 60 * ((r - g) / diff) + 240
- end
- if l == 0 or max == min then
- s = 0
- elseif 0 < 1 and l <= 0.5 then
- s = diff / (2 * l)
- elseif l > 0.5 then
- s = diff / (2 - 2 * l)
- end
- return {
- math.floor(h + 0.5),
- math.floor(s * 100 + 0.5),
- math.floor(l * 100 + 0.5)
- }
- end
- --[[
- @desc hsl转rgb
- @param {number} h
- @param {number} s - css中使用百分比,但该函数需要传整数 50% => 50
- @param {number} l - css中使用百分比,但该函数需要传整数 50% => 50
- @return [number, number, number]
- ]]
- function Color.hsl2rgb(h, s, l)
- h = h % 360
- s = s / 100
- l = l / 100
- local c = (1 - math.abs(2 * l - 1)) * s
- local x = c * (1 - math.abs(((h / 60) % 2) - 1))
- local m = l - c / 2
- local vRGB = {}
- if h >=0 and h < 60 then
- vRGB = {c, x, 0}
- elseif h >= 60 and h < 120 then
- vRGB = {x, c, 0}
- elseif h >= 120 and h < 180 then
- vRGB = {0, c, x}
- elseif h >= 180 and h < 240 then
- vRGB = {0, x, c}
- elseif h >= 240 and h < 300 then
- vRGB = {x, 0, c}
- elseif h >= 300 and h < 360 then
- vRGB = {c, 0, x}
- end
- local r = 255 * (vRGB[1] + m)
- local g = 255 * (vRGB[2] + m)
- local b = 255 * (vRGB[3] + m)
- return {
- math.floor(r + 0.5),
- math.floor(g + 0.5),
- math.floor(b + 0.5)
- }
- end
- --[[
- @desc 将color对象的数据转为rgb格式
- @param {Color} this
- @return {Color} - this
- ]]
- function Color.rgb(this)
- if this.format == 'rgb' then return this end
- if this.format == 'hsl' then
- this.value = Color.hsl2rgb(this.value[1], this.value[2], this.value[3])
- this.format = 'rgb'
- end
- return this
- end
- --[[
- @desc 将color对象的数据转为hsl格式
- @param {Color} this
- @return {Color} - this
- ]]
- function Color.hsl(this)
- if this.format == 'hsl' then return this end
- if this.format == 'rgb' then
- this.value = Color.rgb2hsl(this.value[1], this.value[2], this.value[3])
- this.format = 'hsl'
- end
- return this
- end
- --[[
- @desc 加深一个颜色(明度-)
- @param {Color} this
- @param {number} ratio - 范围:0 ~ 100
- @return {Color} - this
- ]]
- function Color.darken(this, ratio)
- local rgb = this:rgb().value
- this.value = _computeRgb(rgb, '+', ratio)
- return this
- end
- --[[
- @desc 减淡一个颜色(明度+)
- @param {Color} this
- @param {number} ratio - 范围:0 ~ 100
- @return {Color} - this
- ]]
- function Color.lighten(this, ratio)
- local rgb = this:rgb().value
- this.value = _computeRgb(rgb, '-', ratio)
- return this
- end
- --[[
- @desc 提高一个颜色的饱和度
- @param {Color} this
- @param {number} ratio - 范围:0 ~ 100
- @return {Color} - this
- ]]
- function Color.saturate(this, ratio)
- local hsl = this:hsl().value
- this.value[2] = hsl[2] + (100 - hsl[2]) * (ratio / 100)
- if this.value[2] > 100 then this.value[2] = 100 end
- return this
- end
- --[[
- @desc 降低一个颜色的饱和度
- @param {Color} this
- @param {number} ratio - 范围:0 ~ 100
- @return {Color} - this
- ]]
- function Color.desaturate(this, ratio)
- local hsl = this:hsl().value
- this.value[2] = hsl[2] - hsl[2] * (ratio / 100)
- if this.value[2] < 0 then this.value[2] = 0 end
- return this
- end
- --[[
- @desc 混合两个颜色
- @param {Color} this 颜色1
- @param {Color} color 颜色2
- @param {number} weight 颜色1比重 范围:0 ~ 100,默认值为50
- @return {Color} this
- ]]
- function Color.mix(this, color, weight)
- local color1 = this:rgb()
- local color2 = color:rgb()
- local p = weight == nil and 50 or weight
- p = p / 100
- local w = 2 * p - 1
- local a = color1.opacity - color2.opacity
- local w1 = (((w * a == -1) and w or (w + a) / (1 + w * a)) + 1) / 2.0
- local w2 = 1 - w1
- this.value = {
- w1 * color1.value[1] + w2 * color2.value[1],
- w1 * color1.value[2] + w2 * color2.value[2],
- w1 * color1.value[3] + w2 * color2.value[3]
- }
- this:setOpacity(color1.opacity * p + color2.opacity * (1 - p))
- return this
- end
- --[[
- @desc 设置一个值的不透明度
- @param {Color} this
- @param {number} value - 范围:0 ~ 1
- @return {Color} - this
- ]]
- function Color.setOpacity(this, value)
- this.opacity = tonumber(value)
- return this
- end
- --[[
- @desc Gamma校正
- @param {number} r_g_b
- @return {number}
- ]]
- local function adjustGamma(r_g_b)
- if r_g_b <= 0.04045 then return r_g_b / 12.92
- else return ((r_g_b + 0.055) / 1.055) ^ 2.4 end
- end
- --[[
- @desc 获得颜色的相对亮度
- @param {Color} this
- @return {number}
- ]]
- function Color.getRelativeLuminance(this)
- local rgb = this:rgb().value
- return 0.2126 * adjustGamma(rgb[1] / 255) +
- 0.7152 * adjustGamma(rgb[2] / 255) +
- 0.0722 * adjustGamma(rgb[3] / 255)
- end
- --[[
- @desc 获得两颜色的对比度比例
- @param {Color} this
- @param {Color} color
- @return {number}
- ]]
- function Color.getContrastRatio(this, color)
- local ratio = (this:getRelativeLuminance() + 0.05) / (color:getRelativeLuminance() + 0.05)
- if ratio < 1 then return 1 / ratio
- else return ratio end
- end
- --[[
- @desc 检测一个颜色是否为亮色
- @param {Color} this
- @return {boolean}
- ]]
- function Color.isLight(this)
- return this:getRelativeLuminance() > (0.05 * 1.05) ^ 0.5 - 0.05
- end
- --[[
- @desc 检测一个颜色是否为暗色
- @param {Color} this
- @return {boolean}
- ]]
- function Color.isDark(this)
- return this:isLight() == false
- end
- --[[
- @desc 根据范围随机产生一个颜色
- @param {number} [min = 0] - 范围:0 ~ 255
- @param {number} [max = 255] - 范围:0 ~ 255
- @return {Color}
- ]]
- function Color.random(min, max)
- min = min or 0
- max = max or 255
- local rgb = {
- _random(min, max),
- _random(min, max),
- _random(min, max)
- }
- return Color.create(rgb)
- end
- --[[
- @desc 反转一个颜色
- @param {Color} this
- @return {Color} - this
- ]]
- function Color.reverse(this)
- local rgb = this:rgb().value
- for i, v in ipairs(rgb) do
- rgb[i] = math.abs(v - 255)
- end
- return this
- end
- --[[
- @desc 将一个Color实例转化为有效的css颜色值字符串
- @param {Color} this
- @param {('auto' | 'hex' | 'hex-opacity')} [format = 'auto'] - 格式,
- 为auto时,根据Color对象本身的format进行转换,使用对应的css函数,并保留透明度。在调用前应该先执行rgb()或hsl(),以明确输出格式。
- 为hex时,返回hex颜色。无视透明度。
- 为hex-opacity时,返回hex颜色。若不透明度不为1,则假定背景为白色将透明度和颜色进行计算。
- ]]
- function Color.toString(this, format)
- local function toHex(num)
- local int, float = math.modf(num)
- if float > 0.4 then int = int + 1 end
- local zero = ''
- if int < 16 then zero = '0' end
- return zero..string.format('%X', int)
- end
- format = format or 'auto'
- if format == 'auto' then
- if this.format == 'rgb' then
- if this.opacity >= 0 and this.opacity < 1 then
- return 'rgba('..table.concat(this.value, ',')..','..this.opacity..')'
- else
- return 'rgb('..table.concat(this.value, ',')..')'
- end
- elseif this.format == 'hsl' then
- local hsl = this.value
- if this.opacity >= 0 and this.opacity < 1 then
- return string.format('hsla(%s, %s%%, %s%%, %s)', hsl[1], hsl[2], hsl[3], this.opacity)
- else
- return string.format('hsl(%s, %s%%, %s%%)', hsl[1], hsl[2], hsl[3])
- end
- end
- elseif format == 'hex' then
- this:rgb()
- return '#'..toHex(this.value[1])..toHex(this.value[2])..toHex(this.value[3])
- elseif format == 'hex-opacity' then
- this:rgb()
- local r = this.value[1]
- local g = this.value[2]
- local b = this.value[3]
- r = r + r * (1 - this.opacity)
- g = g + g * (1 - this.opacity)
- b = b + b * (1 - this.opacity)
- if r > 255 then r = 255 end
- if g > 255 then g = 255 end
- if b > 255 then b = 255 end
- return '#'..toHex(r)..toHex(g)..toHex(b)
- end
- end
- return Color