-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathColorUtil.lua
More file actions
162 lines (140 loc) · 5.09 KB
/
ColorUtil.lua
File metadata and controls
162 lines (140 loc) · 5.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
-- Enhanced Cooldown Manager addon for World of Warcraft
-- Author: Argium
-- Licensed under the GNU General Public License v3.0
local _, ns = ...
local ColorUtil = {}
ns.ColorUtil = ColorUtil
--- Compares two ECM_Color tables for equality.
---@param c1 ECM_Color|nil
---@param c2 ECM_Color|nil
---@return boolean
function ColorUtil.AreEqual(c1, c2)
if c1 == c2 then
return true
end
if not c1 or not c2 then
return false
end
return c1.r == c2.r and c1.g == c2.g and c1.b == c2.b and c1.a == c2.a
end
local function clamp(v, minV, maxV)
return math.max(minV, math.min(maxV, v))
end
---@param color string|table
---@return number r 0..1
---@return number g 0..1
---@return number b 0..1
local function normalizeRgb(color)
assert(color ~= nil, "ECM.ColorUtil: color is required")
if type(color) == "string" then
local hex = color:gsub("^#", "")
if #hex == 8 then
-- Accept AARRGGBB and ignore alpha.
hex = hex:sub(3, 8)
end
assert(#hex == 6, "ECM.ColorUtil: hex color must be RRGGBB or #RRGGBB")
local r = tonumber(hex:sub(1, 2), 16)
local g = tonumber(hex:sub(3, 4), 16)
local b = tonumber(hex:sub(5, 6), 16)
assert(r and g and b, "ECM.ColorUtil: invalid hex color")
return r / 255, g / 255, b / 255
end
if type(color) == "table" then
local r = assert(tonumber(color.r or color[1]), "ECM.ColorUtil: expected number")
local g = assert(tonumber(color.g or color[2]), "ECM.ColorUtil: expected number")
local b = assert(tonumber(color.b or color[3]), "ECM.ColorUtil: expected number")
-- Treat values > 1 as 0..255.
if r > 1 or g > 1 or b > 1 then
return clamp(r / 255, 0, 1), clamp(g / 255, 0, 1), clamp(b / 255, 0, 1)
end
return clamp(r, 0, 1), clamp(g, 0, 1), clamp(b, 0, 1)
end
error("ECM.ColorUtil: unsupported color type: " .. type(color))
end
---@param r number 0..1
---@param g number 0..1
---@param b number 0..1
---@return string hex
local function rgbToHex(r, g, b)
local ri = clamp(math.floor((r * 255) + 0.5), 0, 255)
local gi = clamp(math.floor((g * 255) + 0.5), 0, 255)
local bi = clamp(math.floor((b * 255) + 0.5), 0, 255)
return string.format("%02x%02x%02x", ri, gi, bi)
end
---@param color ECM_Color
---@return string hex
function ColorUtil.ColorToHex(color)
return rgbToHex(color.r, color.g, color.b)
end
---@param t number 0..1
---@param r1 number
---@param g1 number
---@param b1 number
---@param r2 number
---@param g2 number
---@param b2 number
---@return number r, number g, number b
local function lerpRgb(t, r1, g1, b1, r2, g2, b2)
return (r1 + (r2 - r1) * t), (g1 + (g2 - g1) * t), (b1 + (b2 - b1) * t)
end
---@param t number 0..1
---@param sr number
---@param sg number
---@param sb number
---@param mr number
---@param mg number
---@param mb number
---@param er number
---@param eg number
---@param eb number
---@return number r, number g, number b
local function threeStopGradient(t, sr, sg, sb, mr, mg, mb, er, eg, eb)
if t <= 0 then
return sr, sg, sb
end
if t >= 1 then
return er, eg, eb
end
if t <= 0.5 then
return lerpRgb(t * 2, sr, sg, sb, mr, mg, mb)
end
return lerpRgb((t - 0.5) * 2, mr, mg, mb, er, eg, eb)
end
--- Returns `text` with each character wrapped in a 3-stop gradient color.
---
--- The gradient is computed dynamically so that the start, midpoint, and endpoint colors
--- stay visually consistent for different lengths.
---
--- Notes:
--- - Designed for 4..60 characters; longer strings are mapped onto a 60-step gradient.
--- - Colors can be provided as "RRGGBB" / "#RRGGBB" strings, ECM_Color tables, or {r,g,b} arrays (0..1 or 0..255).
---@param text string
---@param startColor string|table|nil
---@param midColor string|table|nil
---@param endColor string|table|nil
---@return string
function ColorUtil.Sparkle(text, startColor, midColor, endColor)
assert(type(text) == "string", "text must be a string")
startColor = startColor or { r = 0.25, g = 0.82, b = 1.00, a = 1 }
midColor = midColor or { r = 0.62, g = 0.45, b = 1.00, a = 1 }
endColor = endColor or { r = 0.13, g = 0.77, b = 0.37, a = 1 }
local charCount = #text
if charCount == 0 then
return ""
end
local sr, sg, sb = normalizeRgb(startColor)
local mr, mg, mb = normalizeRgb(midColor)
local er, eg, eb = normalizeRgb(endColor)
local effectiveLen = clamp(charCount, 4, 60)
local parts = {}
for i = 1, charCount do
-- Map character index i (1..charCount) to gradient position (1..effectiveLen)
-- with rounding, so each character samples from the correct gradient stop.
local pos = (charCount == 1) and math.ceil(effectiveLen / 2)
or (1 + math.floor(((i - 1) * (effectiveLen - 1) / (charCount - 1)) + 0.5))
local t = (effectiveLen == 1) and 0 or ((pos - 1) / (effectiveLen - 1))
local r, g, b = threeStopGradient(t, sr, sg, sb, mr, mg, mb, er, eg, eb)
parts[i] = "|cff" .. rgbToHex(r, g, b) .. text:sub(i, i) .. "|r"
end
return table.concat(parts)
end