gradientmaker.lua

--- Module which provides for creating color gradients for your text.
-- Original functions found on <a href="https://forums.lusternia.com/discussion/3261/anyone-want-text-gradients">the Lusternia Forums</a>
-- <br> I added functions to work with hecho.
-- <br> I also made performance enhancements by storing already calculated gradients after first use for the session and only including the colorcode in the returned string if the color changed.
-- @module GradientMaker
-- @author Sylphas on the Lusternia forums
-- @author Damian Monogue <demonnic@gmail.com>
-- @copyright 2018 Sylphas
-- @copyright 2020 Damian Monogue
local GradientMaker = {}
local gradient_table = {}

local function _clamp(num1, num2, num3)
  local smaller = math.min(num2, num3)
  local larger = math.max(num2, num3)
  local minimum = math.max(0, smaller)
  local maximum = math.min(255, larger)
  return math.min(maximum, math.max(minimum, num1))
end

local function _gradient(length, rgb1, rgb2)
  assert(length > 0)
  if length == 1 then
    return {rgb1}
  elseif length == 2 then
    return {rgb1, rgb2}
  else
    local step = {}
    for color = 1, 3 do
      step[color] = (rgb2[color] - rgb1[color]) / (length - 2)
    end
    local gradient = {rgb1}
    for iter = 1, length - 2 do
      gradient[iter + 1] = {}
      for color = 1, 3 do
        gradient[iter + 1][color] = math.ceil(rgb1[color] + (iter * step[color]))
      end
    end
    gradient[length] = rgb2
    for index, color in ipairs(gradient) do
      for iter = 1, 3 do
        gradient[index][iter] = _clamp(color[iter], rgb1[iter], rgb2[iter])
      end
    end
    return gradient
  end
end

local function gradient_to_string(gradient)
  local gradstring = ""
  for _, grad in ipairs(gradient) do
    local nodestring = ""
    for _, col in ipairs(grad) do
      nodestring = string.format("%s%03d", nodestring, col)
    end
    if _ == 1 then
      gradstring = nodestring
    else
      gradstring = gradstring .. "|" .. nodestring
    end
  end
  return gradstring
end

local function _gradients(length, ...)
  local arg = {...}
  local argkey = gradient_to_string(arg)
  local gradients_for_length = gradient_table[length]
  if not gradients_for_length then
    gradient_table[length] = {}
    gradients_for_length = gradient_table[length]
  end
  local grads = gradients_for_length[argkey]
  if grads then
    return grads
  end
  if #arg == 0 then
    gradients_for_length[argkey] = {}
    return {}
  elseif #arg == 1 then
    gradients_for_length[argkey] = arg[1]
    return arg[1]
  elseif #arg == 2 then
    gradients_for_length[argkey] = _gradient(length, arg[1], arg[2])
    return gradients_for_length[argkey]
  else
    local quotient = math.floor(length / (#arg - 1))
    local remainder = length % (#arg - 1)
    local gradients = {}
    for section = 1, #arg - 1 do
      local slength = quotient
      if section <= remainder then
        slength = slength + 1
      end
      local gradient = _gradient(slength, arg[section], arg[section + 1])
      for _, rgb in ipairs(gradient) do
        table.insert(gradients, rgb)
      end
    end
    gradients_for_length[argkey] = gradients
    return gradients
  end
end

local function _color_name(rgb)
  local least_distance = math.huge
  local cname = ""
  for name, color in pairs(color_table) do
    local color_distance = math.sqrt((color[1] - rgb[1]) ^ 2 + (color[2] - rgb[2]) ^ 2 + (color[3] - rgb[3]) ^ 2)
    if color_distance < least_distance then
      least_distance = color_distance
      cname = name
    end
  end
  return cname
end

local function errorIfEmpty(text, funcName)
  assert(#text > 0, string.format("%s: you passed in an empty string, and I cannot make a gradient out of an empty string", funcName))
end

local function dgradient_table(text, ...)
  errorIfEmpty(text, "dgradient_table")
  local gradients = _gradients(#text, ...)
  local dgrads = {}
  for character = 1, #text do
    table.insert(dgrads, {gradients[character], text:sub(character, character)})
  end
  return dgrads
end

local function dgradient(text, ...)
  errorIfEmpty(text, "dgradient")
  local gradients = _gradients(#text, ...)
  local dgrad = ""
  local current_color = ""
  for character = 1, #text do
    local new_color = "<" .. table.concat(gradients[character], ",") .. ">"
    local char = text:sub(character, character)
    if new_color == current_color then
      dgrad = dgrad .. char
    else
      dgrad = dgrad .. new_color .. char
      current_color = new_color
    end
  end
  return dgrad
end

local function cgradient_table(text, ...)
  errorIfEmpty(text, "cgradient_table")
  local gradients = _gradients(#text, ...)
  local cgrads = {}
  for character = 1, #text do
    table.insert(cgrads, {_color_name(gradients[character]), text:sub(character, character)})
  end
  return cgrads
end

local function cgradient(text, ...)
  errorIfEmpty(text, "cgradient")
  local gradients = _gradients(#text, ...)
  local cgrad = ""
  local current_color = ""
  for character = 1, #text do
    local new_color = "<" .. _color_name(gradients[character]) .. ">"
    local char = text:sub(character, character)
    if new_color == current_color then
      cgrad = cgrad .. char
    else
      cgrad = cgrad .. new_color .. char
      current_color = new_color
    end
  end
  return cgrad
end

local hex = Geyser.Color.hex

local function hgradient_table(text, ...)
  errorIfEmpty(text, "hgradient_table")
  local grads = _gradients(#text, ...)
  local hgrads = {}
  for character = 1, #text do
    table.insert(hgrads, {hex(unpack(grads[character])):sub(2, -1), text:sub(character, character)})
  end
  return hgrads
end

local function hgradient(text, ...)
  errorIfEmpty(text, "hgradient")
  local grads = _gradients(#text, ...)
  local hgrads = ""
  local current_color = ""
  for character = 1, #text do
    local new_color = hex(unpack(grads[character]))
    local char = text:sub(character, character)
    if new_color == current_color then
      hgrads = hgrads .. char
    else
      hgrads = hgrads .. new_color .. char
      current_color = new_color
    end
  end
  return hgrads
end

local function color_name(...)
  local arg = {...}
  if #arg == 1 then
    return _color_name(arg[1])
  elseif #arg == 3 then
    return _color_name(arg)
  else
    local errmsg =
      "color_name: You must pass either a table of r,g,b values: color_name({r,g,b})\nor the three r,g,b values separately: color_name(r,g,b)"
    error(errmsg)
  end
end

--- Returns the closest color name to a given r,g,b color
-- @param r The red component. Can also pass the full color as a table, IE { 255, 0, 0 }
-- @param g The green component. If you pass the color as a table as noted above, this param should be empty
-- @param b the blue components. If you pass the color as a table as noted above, this param should be empty
-- @usage
-- closest_color = GradientMaker.color_name(128,200,30) -- returns "ansi_149"
-- closest_color = GradientMaker.color_name({128, 200, 30}) -- this is functionally equivalent to the first one
function GradientMaker.color_name(...)
  return color_name(...)
end

--- Returns the text, with the defined color gradients applied and formatted for us with decho. Usage example below produces the following text
-- <br><img src="https://demonnic.github.io/mdk/images/dechogradient.png" alt="dgradient example">
-- @tparam string text The text you want to apply the color gradients to
-- @param first_color The color you want it to start at. Table of colors in { r, g, b } format
-- @param second_color The color you want the gradient to transition to first. Table of colors in { r, g, b } format
-- @param next_color Keep repeating if you want it to transition from the second color to a third, then a third to a fourth, etc
-- @see cgradient
-- @see hgradient
-- @usage
-- decho(GradientMaker.dgradient("a luminescent butterly floats about lazily on brillant blue and lilac wings\n", {255,0,0}, {255,128,0}, {255,255,0}, {0,255,0}, {0,255,255}, {0,128,255}, {128,0,255}))
-- decho(GradientMaker.dgradient("a luminescent butterly floats about lazily on brillant blue and lilac wings\n", {255,0,0}, {0,0,255}))
-- decho(GradientMaker.dgradient("a luminescent butterly floats about lazily on brillant blue and lilac wings\n", {50,50,50}, {0,255,0}, {50,50,50}))
function GradientMaker.dgradient(text, ...)
  return dgradient(text, ...)
end

--- Returns the text, with the defined color gradients applied and formatted for us with cecho. Usage example below produces the following text
-- <br><img src="https://demonnic.github.io/mdk/images/cechogradient.png" alt="cgradient example">
-- @tparam string text The text you want to apply the color gradients to
-- @param first_color The color you want it to start at. Table of colors in { r, g, b } format
-- @param second_color The color you want the gradient to transition to first. Table of colors in { r, g, b } format
-- @param next_color Keep repeating if you want it to transition from the second color to a third, then a third to a fourth, etc
-- @see dgradient
-- @see hgradient
-- @usage
-- cecho(GradientMaker.cgradient("a luminescent butterly floats about lazily on brillant blue and lilac wings\n", {255,0,0}, {255,128,0}, {255,255,0}, {0,255,0}, {0,255,255}, {0,128,255}, {128,0,255}))
-- cecho(GradientMaker.cgradient("a luminescent butterly floats about lazily on brillant blue and lilac wings\n", {255,0,0}, {0,0,255}))
-- cecho(GradientMaker.cgradient("a luminescent butterly floats about lazily on brillant blue and lilac wings\n", {50,50,50}, {0,255,0}, {50,50,50}))
function GradientMaker.cgradient(text, ...)
  return cgradient(text, ...)
end

--- Returns the text, with the defined color gradients applied and formatted for us with hecho. Usage example below produces the following text
-- <br><img src="https://demonnic.github.io/mdk/images/hechogradient.png" alt="hgradient example">
-- @tparam string text The text you want to apply the color gradients to
-- @param first_color The color you want it to start at. Table of colors in { r, g, b } format
-- @param second_color The color you want the gradient to transition to first. Table of colors in { r, g, b } format
-- @param next_color Keep repeating if you want it to transition from the second color to a third, then a third to a fourth, etc
-- @see cgradient
-- @see dgradient
-- @usage
-- hecho(GradientMaker.hgradient("a luminescent butterly floats about lazily on brillant blue and lilac wings\n", {255,0,0}, {255,128,0}, {255,255,0}, {0,255,0}, {0,255,255}, {0,128,255}, {128,0,255}))
-- hecho(GradientMaker.hgradient("a luminescent butterly floats about lazily on brillant blue and lilac wings\n", {255,0,0}, {0,0,255}))
-- hecho(GradientMaker.hgradient("a luminescent butterly floats about lazily on brillant blue and lilac wings\n", {50,50,50}, {0,255,0}, {50,50,50}))
function GradientMaker.hgradient(text, ...)
  return hgradient(text, ...)
end

--- Returns a table, each element of which is a table, the first element of which is the color name to use and the character which should be that color
-- @tparam string text The text you want to apply the color gradients to
-- @param first_color The color you want it to start at. Table of colors in { r, g, b } format
-- @param second_color The color you want the gradient to transition to first. Table of colors in { r, g, b } format
-- @param next_color Keep repeating if you want it to transition from the second color to a third, then a third to a fourth, etc
-- @see cgradient
function GradientMaker.cgradient_table(text, ...)
  return cgradient_table(text, ...)
end

--- Returns a table, each element of which is a table, the first element of which is the color({r,g,b} format) to use and the character which should be that color
-- @tparam string text The text you want to apply the color gradients to
-- @param first_color The color you want it to start at. Table of colors in { r, g, b } format
-- @param second_color The color you want the gradient to transition to first. Table of colors in { r, g, b } format
-- @param next_color Keep repeating if you want it to transition from the second color to a third, then a third to a fourth, etc
-- @see dgradient
function GradientMaker.dgradient_table(text, ...)
  return dgradient_table(text, ...)
end

--- Returns a table, each element of which is a table, the first element of which is the color(in hex) to use and the second element of which is the character which should be that color
-- @tparam string text The text you want to apply the color gradients to
-- @param first_color The color you want it to start at. Table of colors in { r, g, b } format
-- @param second_color The color you want the gradient to transition to first. Table of colors in { r, g, b } format
-- @param next_color Keep repeating if you want it to transition from the second color to a third, then a third to a fourth, etc
-- @see hgradient
function GradientMaker.hgradient_table(text, ...)
  return hgradient_table(text, ...)
end

--- Creates global copies of the c/d/hgradient(_table) functions and color_name for use without accessing the module table
-- @usage
-- GradientMaker.install_global()
-- cecho(cgradient(...)) -- use cgradient directly now
function GradientMaker.install_global()
  _G["hgradient"] = function(...)
    return hgradient(...)
  end
  _G["dgradient"] = function(...)
    return dgradient(...)
  end
  _G["cgradient"] = function(...)
    return cgradient(...)
  end
  _G["hgradient_table"] = function(...)
    return hgradient_table(...)
  end
  _G["dgradient_table"] = function(...)
    return dgradient_table(...)
  end
  _G["cgradient_table"] = function(...)
    return cgradient_table(...)
  end
  _G["color_name"] = function(...)
    return color_name(...)
  end
end

-- function GradientMaker.getGrads()
--   return gradient_table
-- end

return GradientMaker
generated by LDoc 1.5.0 Last updated 2023-05-29 18:41:27