Module:ColorInfobox

There are no reviewed versions of this page, so it may not have been checked for adherence to standards.

Documentation for this module may be created at Module:ColorInfobox/doc

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 vaildPartRgb, vaildAllRgb = false, true
  for i = 1, 3 do
    local rgbPartInt = tonumber(args[i])
    if rgbPartInt == nil then vaildAllRgb = false
    else
      if 360 < rgbPartInt or rgbPartInt < 0 then return '[Error ColorCodes]: 引数1~3には0~360の値を指定して下さい。' end
      vaildPartRgb = true
      rgb[i] = rgbPartInt
    end
  end

  -- カラーコードの指定が部分的に欠けていた場合
  if vaildPartRgb and not vaildAllRgb then return '[Error ColorCodes]:引数1~3には半角英数字のみを使用して下さい。' 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 '&#35;'

  for codeName, code in pairs(rgbToColorCodes(rgb)) do
    if codeName == 'hex' then
      txt = txt:gsub('$hex', vaildAllRgb and hexHead .. code or '') -- HEXのみ個別replace
    elseif codeName == 'omitHex' then
      txt = txt:gsub('$HEX', vaildAllRgb and hexHead .. code or '') -- 省略hexのみ個別replace
    else
      -- format関数を決定
      local format = formatLs[codeName] or { through, through, through }
      -- replace START
      txt = txt:gsub('(%$' .. codeName:upper() .. ')', function()
        local replace = ''
        if not vaildPartRgb then return replace end --rgb値が異常な場合、カラーコード指定を削除
        for i = 1, #code do
          local formated = format[i](code[i])
          replace = replace .. (i == 1 and '' or args.join or ',&nbsp;') .. 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, vaildAllRgb and format[i](partCode) or '')
      end
      -- 部分replace END
    end
  end

  return txt
end

return p