モジュール:ColorCodes
ナビゲーションに移動
検索に移動
local p = {}
local getArgs = require('Module:Arguments').getArgs
function p.error(message)
return '<strong class="error">エラー:' .. message .. '</strong>'
end
-- @param n: number
-- @returns: number
function round(n)
local dec = n % 1
local int = n - dec
return dec < 0.5 and int or int + 1
end
-- @param n: number
-- @param place: number
-- @returns: number
function roundDec(n, place) return round(n * 10 ^ place) / 10 ^ place end
-- @param n: number
-- @param range: number
-- @returns: number
function inRange(n, range) return n < 0 and n + range or n end
-- @param: number
-- @returns: { max = number, min = number, maxIdx = int, minIdx = int }
function maxMin(arr)
local res = { max = 0, min = 1 / 0, maxIdx = 0, minIdx = 0 }
for i, v in pairs(arr) do
if res.max < v then res.max, res.maxIdx = v, i end
if res.min > v then res.min, res.minIdx = v, i end
end
return res
end
-- @param rgb: { int, int, int }
-- @returns: { hsv = {number,number,number}, hsl = {number,number,number}, hwb = {number,number,number}
-- rgb = {int,int,int}, lab = {number,number,number}, hex = string, omitHex = string }
function rgbToColorCodes(rgb)
local hex = ''
local rgbMaxMin = maxMin(rgb)
rgbMaxMin.diff = rgbMaxMin.max - rgbMaxMin.min
local maxRgbIdx = rgbMaxMin.maxIdx
local rgbRatio = { rgb[1] / 255, rgb[2] / 255, rgb[3] / 255 }
-- hexを求める
local omitHex = ''
for i = 1, 3 do
local hexPart = string.format("%x", rgb[i])
if #hexPart == 1 then hexPart = '0' .. hexPart end
local partHex = hexPart:sub(1, 1)
local omitablePartHex = partHex == hexPart:sub(2, 2)
omitHex = (omitHex and omitablePartHex and omitHex .. partHex) or false
hex = hex .. hexPart
end
omitHex = omitHex or hex
-- h(色相)を求める
local h = 0
if (rgb[1] == rgb[2] and rgb[1] == rgb[3]) then
h = 0
else
local hNumerators = { 0, 0 }
for i = 1, 2 do
local calc = maxRgbIdx - i
hNumerators[i] = rgb[calc < 1 and calc + 3 or calc]
end
h = ((hNumerators[2] - hNumerators[1]) / rgbMaxMin.diff + (maxRgbIdx - 1) * 2) * 60
h = h < 0 and h + 360 or h
end
local hsv, hsl = { h, 0, 0 }, { h, 0, 0 }
-- hsvを求める
hsv[2] = rgbMaxMin.max == 0 and 0 or rgbMaxMin.diff / rgbMaxMin.max
hsv[3] = rgbMaxMin.max / 255
-- hslを求める
local hslCalc = { 1, 0 }
hsl[3] = (rgbMaxMin.max + rgbMaxMin.min) / (2 * 255)
if 127 < hsl[3] then hslCalc = { -1, -510 } end
local divver = rgbMaxMin.max + rgbMaxMin.min + hslCalc[2]
hsl[2] = divver == 0 and 0 or hslCalc[1] * rgbMaxMin.diff / divver
-- hwbを求める
local hwb = { h, rgbMaxMin.min, 1 - rgbMaxMin.max }
for i = 2, 3 do hwb[i] = inRange(hwb[i], 255) / 255 end
-- rgbから平均的なcmykを求めたい 以下は精度の低い近似
-- local cmyk = { 0, 0, 0, 0 }
-- cmyk[4] = 1 - maxMin(rgbRatio).max
-- for i = 1, 3 do cmyk[i] = (1 - rgbRatio[i] - cmyk[4]) / (1 - cmyk[4]) end
-- リニアRGBを求める
local rgbLinear = { 0, 0, 0 }
for i = 1, 3 do
if 0.04045 < rgbRatio[i] then
rgbLinear[i] = ((rgbRatio[i] + 0.055) / 1.055) ^ (2.4)
else rgbLinear[i] = rgbRatio[i] / 12.92 end
end
-- xyzを求める
local sRgbToLabCoefMatrix = {
{ 0.4124, 0.3576, 0.1805 },
{ 0.2126, 0.7152, 0.0722 },
{ 0.0193, 0.1192, 0.9505 }
}
local xyz = { 0, 0, 0 }
-- sRgbToLabCoefMatrix * rgbLinear の行列積を計算
for rowIdx, row in pairs(sRgbToLabCoefMatrix) do
for colIdx, coef in pairs(row) do
xyz[rowIdx] = xyz[rowIdx] + coef * rgbLinear[colIdx]
end
end
local function labF(x)
if x > (6 / 29) ^ 3 then return 116 * x ^ (1 / 3) - 16
else return x * (29 / 3) ^ 3 end
end
-- labを求める
local whiteXyz = { 0.9505, 1, 1.089 }
local xyzMapedlabF = { 0, 0, 0 }
for i = 1, 3 do xyzMapedlabF[i] = labF(xyz[i] / whiteXyz[i]) end
local lab = {
xyzMapedlabF[2],
(500 / 116) * (xyzMapedlabF[1] - xyzMapedlabF[2]),
(200 / 116) * (xyzMapedlabF[2] - xyzMapedlabF[3])
}
-- lchを求めたい
-- local lch = {
-- lab[1],
-- (lab[2] ^ 2 + lab[3] ^ 2) ^ (0.5),
-- inRange(math.deg(math.atan(lab[3], lab[2])), 360) -- ここだけ何故か数が合わない
-- }
return { hex = hex, omitHex = omitHex, hsv = hsv, hsl = hsl, lab = lab, hwb = hwb, xyz = xyz, rgb = rgb }
end
function p.main(frame)
local args = getArgs(frame, {
wrappers = 'Template:ColorCodes', trim = false, removeBlanks = false
})
local rgb = { 0, 0, 0 }
local errmsg = '[Error ColorCodes]:引数1~3には0~255の半角数字のみを指定して下さい。'
for i = 1, 3 do
local rgbPartInt = tonumber(args[i])
if rgbPartInt == nil then return args.ifErr or errmsg end
if 360 < rgbPartInt or rgbPartInt < 0 then return errmsg end
rgb[i] = rgbPartInt
end
local txt = args[4] or args.txt or [[ debug rgb: $RGB
hex: $hex <br> 省略hex : $HEX
hsv: $HSV <br> hsl: $HSL
xyz: $XYZ <br> lab: $LAB
hwb: $HWB ]]
local function through(v) return v end
local function roundPer(v) return round(v * 100) end
local function roundTwoDec(v) return roundDec(v, 2) end
-- 計算された色の小数値をフォーマットする
local formatType = {
hsv = { round, roundPer, roundPer },
xyz = { roundPer, roundPer, roundPer },
lab = { roundTwoDec, roundTwoDec, roundTwoDec },
}
local formatLs = {
hsv = formatType.hsv,
hsl = formatType.hsv,
xyz = formatType.xyz,
lab = formatType.lab,
hwb = formatType.hsv
}
local hexHead = args.hexHead or '#'
for codeName, code in pairs(rgbToColorCodes(rgb)) do
if codeName == 'hex' then txt = txt:gsub('$hex', hexHead .. code) -- HEXのみ個別replace
elseif codeName == 'omitHex' then txt = txt:gsub('$HEX', hexHead .. code) -- 省略hexのみ個別replace
else
-- format関数を決定
local format = formatLs[codeName] or { through, through, through }
-- replace START
txt = txt:gsub('(%$' .. codeName:upper() .. ')', function()
local replace = ''
for i = 1, #code do
local formated = format[i](code[i])
replace = replace .. (i == 1 and '' or args.join or ', ') .. tostring(formated) -- 区切り文字を挟んで数値を並べる
end
return replace
end)
-- replace END
-- 部分replace START
for i, partCode in pairs(code) do
local partCodeName = codeName:gsub(codeName:sub(i, i), string.upper)
txt = txt:gsub('$' .. partCodeName, format[i](partCode))
end
-- 部分replace END
end
end
return txt
end
return p