Lua - xterm 256 色渐变脚本

Lua - xterm 256 colors gradient scripting

这一直困扰着我,我不确定是否有答案。我知道有像 Love2D 这样的模块可以实现渐变,我猜它使用的是 RGB 着色。但是,我需要使用 xterm 256 色找到非常相似的东西,但我似乎无法在任何地方找到帮助解决此问题的渐变图。

我的猜测是我必须创建一个 "nearest to RGB color" 并从中创建一个渐变,将相应的 RBG 与最近的 xterm 匹配相匹配,但老实说,我什至不知道从哪里开始。我知道 Python 中有一个 "convert xterm to RGB hex" 脚本(位于 here),但由于我不知道 Python,我不知道如何将其转换为 Lua.

最终,我想做的是能够将文本或多或少地变成彩虹渐变。我目前有一个 return xterm 颜色的函数,但它是完全随机的,输出可能有点难读。这是我对该代码的看法。 @x 代表 "convert to xterm color",后面跟着一个三位数代码(001 到 255),然后是文本。

function rainbow(text)
    local rtext = ""
    local xcolor = 1
    local sbyte = 1
    for i = 1, #text do
        math.randomseed(os.time() * xcolor * sbyte)
        sbyte = string.byte(i)
        xcolor = math.random(1, 255)
        rtext = rtext .. "@x" .. string.rep("0", 3 - string.len(xcolor)) .. xcolor .. text:sub(i,i)
    end
    return rtext
end

因此,例如,print(rainbow("Test")) 将导致:

@x211T@x069e@x154s@x177t

显然,这不是渐变,也不是我想要的结果。我想要的是可能的,还是失败的原因?

编辑
我知道 256 色的局限性,而且我知道没有太多回旋余地。正如评论中指出的那样,会有很多相同的颜色匹配,所以我会得到一串相同颜色的字符串。这对我来说很好,真的。无论它进行了多少次实际转换,我都希望它能够接近地模拟渐变。

如果我能够至少正确地创建颜色组而无需轮询图表,那就太好了。我想我可能最终不得不做的是创建一个 "compatible colors schemes" 的 table 并使用它,除非有人有更好的主意。

定义nearest_term256_color_index函数:

local abs, min, max, floor = math.abs, math.min, math.max, math.floor
local levels = {[0] = 0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff}

local function index_0_5(value) -- value = color component 0..255
   return floor(max((value - 35) / 40, value / 58))
end

local function nearest_16_231(r, g, b)   -- r, g, b = 0..255
   -- returns color_index_from_16_to_231, appr_r, appr_g, appr_b
   r, g, b = index_0_5(r), index_0_5(g), index_0_5(b)
   return 16 + 36 * r + 6 * g + b, levels[r], levels[g], levels[b]
end

local function nearest_232_255(r, g, b)  -- r, g, b = 0..255
   local gray = (3 * r + 10 * g + b) / 14
   -- this is a rational approximation for well-known formula
   -- gray = 0.2126 * r + 0.7152 * g + 0.0722 * b
   local index = min(23, max(0, floor((gray - 3) / 10)))
   gray = 8 + index * 10
   return 232 + index, gray, gray, gray
end

local function color_distance(r1, g1, b1, r2, g2, b2)
   return abs(r1 - r2) + abs(g1 - g2) + abs(b1 - b2)
end

local function nearest_term256_color_index(r, g, b)   -- r, g, b = 0..255
   local idx1, r1, g1, b1 = nearest_16_231(r, g, b)
   local idx2, r2, g2, b2 = nearest_232_255(r, g, b)
   local dist1 = color_distance(r, g, b, r1, g1, b1)
   local dist2 = color_distance(r, g, b, r2, g2, b2)
   return dist1 < dist2 and idx1 or idx2
end

定义 generate_gradient 函数,在您的文本中插入 @x...

local unpack, tonumber = table.unpack or unpack, tonumber

local function convert_color_to_table(rrggbb)
   if type(rrggbb) == "string" then
      local r, g, b = rrggbb:match"(%x%x)(%x%x)(%x%x)"
      return {tonumber(r, 16), tonumber(g, 16), tonumber(b, 16)}
   else
      return rrggbb
   end
end

local function round(x)
   return floor(x + 0.5)
end

local function generate_gradient(text, first_color, last_color)
   local r, g, b = unpack(convert_color_to_table(first_color))
   local dr, dg, db = unpack(convert_color_to_table(last_color))
   local char_pattern = "[^8-1][8-1]*"
   local n = max(1, select(2, text:gsub(char_pattern, "")) - 1)
   dr, dg, db = (dr - r)/n, (dg - g)/n, (db - b)/n
   local result = ""
   for c in text:gmatch(char_pattern) do
      result = result..("@x%03d"):format(nearest_term256_color_index(
         round(r), round(g), round(b)))..c
      r, g, b = r + dr, g + dg, b + db
   end
   return result
end

在终端内测试:

local function print_with_colors(str)
   print(
      str:gsub("@x(%d%d%d)",
         function(color_idx)
            return "[38;5;"..color_idx.."m"
         end)
      .."[0m"
   )
end

local str = "Gradient"
local blue, red = {0, 0, 255}, "#FF0000"
str = generate_gradient(str, blue, red)  -- gradient from blue to red
print(str)
print_with_colors(str)

rainbow()不是渐变,而是几个渐变的链。

我已经完成了。仅使用 ANSI 256 颜色转义码模式。

-- 参考 ANSI 256 颜色 = https://robotmoon.com/256-colors/ str = '\27[38;5;34mG[38;5;35mr[38;5;36ma[38;5;37md[38;5; 38mi[38;5;39me[38;5;50mn[38;5;51mt'

if f then f.Destroy() end
f=createForm()
pb=createPaintBox(f)
pb.Canvas.Font.Size=52
pb.Align='alClient'

local rect={}
rect.Left=0
rect.Top=0
rect.Right=pb.Width
rect.Bottom=pb.Height

pb.Repaint()
pb.Canvas.Font.Style='fsBold'
pb.Canvas.Font.Name='Trebuchet MS'
pb.Canvas.Font.Color = 0
pb.Canvas.textRect(rect,16,54,'Gradient')
pb.Canvas.textRect(rect,12,50,str)

感谢@Egor Skriptunoff 的启发。