Repository navigation
Expand file tree
/
Copy pathSFUtils_Color.lua
More file actions
422 lines (350 loc) · 12.2 KB
/
Copy pathSFUtils_Color.lua
File metadata and controls
422 lines (350 loc) · 12.2 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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
--[[
This module extends LibSFUtils with a suite of functions for converting between color
representations (RGB floats, Hex strings, and ESO color tags) and a dedicated SF_Color
class for managing color state. It is optimized for performance in the ESO addon
environment, minimizing runtime calculations by caching hex values.
Key Features:
Format Flexibility: Handles rrggbb, aarrggbb, and |crrggbb hex strings automatically.
Robust Conversion: Safe handling of nil inputs and mixed integer/float inputs.
Object-Oriented Color: SF_Color class with methods for cloning, comparing, and colorizing text.
Performance: Caches hex strings to avoid repeated formatting calculations.
Technical Notes
Alpha Handling: The SF_Color object stores alpha internally, but the hex property (and ToHex())
only stores the 6-character RGB hex. This is intentional for ESO text tags (|cRRGGBB), which
do not support alpha in the tag itself. Alpha must be handled by the UI control (e.g., SetTextColor)
if transparency is needed for the text element itself.
Input Ambiguity: The SetColor method resolves ambiguity between 0–1 and 0–255 ranges by checking
the first argument (r). If r > 1, it assumes all numeric arguments are integers. Mixing
types (e.g., r=0.5, g=200) is discouraged and may yield unexpected results.
Performance: The library avoids creating ZO_ColorDef objects unless explicitly requested (ToZO_ColorDef),
reducing overhead in loops or frequent UI updates.
--]]
-- LibSFUtils should already be defined in prior loaded file
local sfutil = LibSFUtils
assert(sfutil, "LibSFUtils_Global must be loaded before this file")
local zo_floor = zo_floor
---------------------
--[[
sfutil.colorRGBToHex(r, g, b)
Converts RGB float values (0–1) to a 6-character hex string (RRGGBB).
Parameters:
r, g, b (number): Float values between 0 and 1.
Note: If any value is nil, it defaults to 1 (White).
Returns: String (e.g., "FF0000").
Returns nil if all three inputs are explicitly nil (though the default logic usually prevents this).
]]
function sfutil.colorRGBToHex(r, g, b)
if not r or not g or not b then return nil end
return string.format("%.2x%.2x%.2x", zo_floor((tonumber(r) or 1) * 255),
zo_floor((tonumber(g) or 1) * 255), zo_floor((tonumber(b) or 1) * 255))
end
--[[
sfutil.colorHexToRGBA(colourString)
Converts a 6-character hex string (rrggbb) to RGB float values.
Parameters: colourString (string).
Returns: r, g, b, a (four numbers between 0 and 1).
Defaults: Returns 1, 1, 1, 1 (White, opaque) if input is nil or invalid.
Note: This function does not support alpha in the input string. Prefer sfutil.ConvertHexToRGBA for flexibility.
--]]
function sfutil.colorHexToRGBA(colourString)
if not colourString then
return 1,1,1,1
end
local r=tonumber(string.sub(colourString, 1, 2), 16) or 255
local g=tonumber(string.sub(colourString, 3, 4), 16) or 255
local b=tonumber(string.sub(colourString, 5, 6), 16) or 255
return r/255, g/255, b/255, 1
end
--[[
sfutil.ConvertRGBToHex(r, g, b)
Converts RGB floats to an ESO color tag string (|cRRGGBB). We could use
ZO_ColorDef to build this, but we use so many colors, we won't do it.
Parameters: r, g, b (number).
Safety: If any input is nil, it defaults to 1 (White).
Returns: String (e.g., "|cFF0000").
Note: This is NOT the same as the LibSFUtils.colorRGBToHex() function!
--]]
function sfutil.ConvertRGBToHex(r, g, b)
r = r or 1
g = g or 1
b = b or 1
return string.format("|c%.2x%.2x%.2x", zo_floor(r * 255), zo_floor(g * 255), zo_floor(b * 255))
end
--[[
sfutil.ConvertHexToRGBA(colourString)
Converts various hex string formats to RGB float values with alpha support.
Supported Formats:
|crrggbb (ESO tag format)
aarrggbb (8-char with alpha)
rrggbb (6-char standard)
Parameters: colourString (string).
Returns: r, g, b, a (numbers 0–1).
Defaults: Returns 1, 1, 1, 1 for invalid inputs or non-string types.
--]]
function sfutil.ConvertHexToRGBA(colourString)
if type(colourString) ~= "string" then
return 1,1,1,1
end
local r, g, b, a
if string.sub(colourString,1,1) == "|" then
-- format "|crrggbb"
r=tonumber(string.sub(colourString, 3, 4), 16) or 255
g=tonumber(string.sub(colourString, 5, 6), 16) or 255
b=tonumber(string.sub(colourString, 7, 8), 16) or 255
a = 255
elseif #colourString == 8 then
-- format "aarrggbb"
a=tonumber(string.sub(colourString, 1, 2), 16) or 255
r=tonumber(string.sub(colourString, 3, 4), 16) or 255
g=tonumber(string.sub(colourString, 5, 6), 16) or 255
b=tonumber(string.sub(colourString, 7, 8), 16) or 255
elseif #colourString == 6 then
-- format "rrggbb"
r=tonumber(string.sub(colourString, 1, 2), 16) or 255
g=tonumber(string.sub(colourString, 3, 4), 16) or 255
b=tonumber(string.sub(colourString, 5, 6), 16) or 255
a = 255
else
-- unidentified format
r = 255
g = 255
b = 255
a = 255
end
return r/255, g/255, b/255, a/255
end
--[[
sfutil.ConvertHexToRGBAPacked(colourString)
Convenience wrapper that returns the result of ConvertHexToRGBA as a table.
Returns: Table {r = ..., g = ..., b = ..., a = ...}.
--]]
function sfutil.ConvertHexToRGBAPacked(colourString)
local r, g, b, a = sfutil.ConvertHexToRGBA(colourString)
return {r = r, g = g, b = b, a = a}
end
-- ------------------------------------------
--[[
SF_Color Class
A lightweight object for storing and manipulating color data. It caches the hex
representation to optimize text rendering.
--]]
SF_Color = {}
SF_Color.__index = SF_Color
--[[
color:__call(text)
Allows the object to be called like a function.
Usage: myColor("Hello") is equivalent to myColor:Colorize("Hello").
--]]
SF_Color.__call = function(self, text)
return self:Colorize(text)
end
--[[
Don't want to make this public because it can leave
SF_Color in an inconsistant state - hex is not set
from these values. We just assume that has been or
will be taken care of.
--]]
local function setRGB(sfcolor, r, g, b, a)
r = r>1 and r/255 or r
g = g>1 and g/255 or g
b = b>1 and b/255 or b
a = a>1 and a/255 or a
sfcolor.rgb.r = r or 1
sfcolor.rgb.g = g or 1
sfcolor.rgb.b = b or 1
sfcolor.rgb.a = a or 1
end
--[[ ---------------------
Create a color object.
This is a storage container for:
hex - a 6-character hex representation of the RGB color
rgb - a table containing the float values for r, g, b, a (values btwn 0-1)
with some handy related functions.
Why not use the already existing ZO_ColorDef? The intention is to have an object
which does not do as much calculation behind the scenes with every use - with the
intention of optimizing speed at the expense of a little extra memory.
The capability to convert between one and the other is provided.
SF_Color:New(pr, pg, pb, pa)
Parameters options:
pr, pg, pb, pa - The RGB floats between 0-1.
Missing (nil) rgba values will be set to 1.
pr - The hex value (6-character string rrggbb) to set the color to.
pr - The hex value (8-character string aarrggbb) to set the color to.
pr - Another SF_Color object to copy values from.
pr - A ZO_ColorDef to copy/calculate values from.
Returns: New SF_Color object.
--]]
function SF_Color:New(pr, pg, pb, pa)
local c = setmetatable({},SF_Color)
c.rgb = {r=1, g=1, b=1, a=1}
SF_Color.SetColor(c, pr, pg, pb, pa)
return c
end
--[[
SF_Color:Initialize(pr, pg, pb, pa)
Resets an existing SF_Color object to a new color.
Usage: Useful for reusing objects to reduce garbage collection.
Parameters: Same as New.
--]]
function SF_Color:Initialize(pr, pg, pb, pa)
self.rgb = {r=1, g=1, b=1, a=1}
self:SetColor(pr, pg, pb, pa)
end
--[[
color:UnpackRGB()
Returns: r, g, b (0–1).
--]]
function SF_Color:UnpackRGB()
if not self.rgb and self.r then
return self.r, self.g, self.b
end
return self.rgb.r, self.rgb.g, self.rgb.b
end
--[[
color:UnpackRGBA()
Returns RGBA float values.
Returns: r, g, b, a (0–1).
Safety: Handles both SF_Color rgb table structure and ZO_ColorDef r/g/b direct properties.
--]]
function SF_Color:UnpackRGBA()
if self.r then
-- is a ZO_ColorDef type
return self.r, self.g, self.b, self.a or 1
elseif self.rgb then
-- is a SF_Color type
return self.rgb.r or 1, self.rgb.g or 1, self.rgb.b or 1, self.rgb.a or 1
end
return 1, 1, 1, 1
end
--[[
color:SetAlpha(a)
Updates only the alpha channel.
Parameters: a (number 0–1 or 0–255; auto-converted).
--]]
function SF_Color:SetAlpha(a)
if self.rgb then
self.rgb.a = a
elseif self.r then
self.a = a
end
end
--[[
Set a color object to a particular color value.
color:SetColor(r, g, b, a)
Parameters:
pr, pg, pb, pa (number)- The RGB floats between 0-1.
Missing (nil) rgba values will be set to 1.
pr (string) - The hex value (6-character string) to set the color to.
pr (table) - Another SF_Color object to copy values from.
pr (table) - A ZOS ZO_ColorDef to copy/calculate values from.
String: Interprets as Hex.
Table: Interprets as SF_Color or ZO_ColorDef (copies values).
Number:
If r > 1: Assumes all inputs are integers (0–255) and converts to floats.
If r <= 1: Assumes all inputs are floats (0–1).
Returns: self (for chaining).
--]]
function SF_Color:SetColor(r, g, b, a)
if type(r) == "string" then
-- r is hex value
self.hex = r
self.rgb.r, self.rgb.g, self.rgb.b, self.rgb.a = sfutil.ConvertHexToRGBA(r)
elseif type(r) == "table" then
if r.r ~= nil then
-- r is ZO_ColorDef
setRGB(self, r:UnpackRGBA())
self.hex = sfutil.colorRGBToHex(r:UnpackRGB())
else
-- r is SF_Color we are copying
setRGB(self, r:UnpackRGBA())
self.hex = r.hex
end
elseif type(r) == "number" then
-- Determine scale based on the FIRST argument only to avoid mixed input ambiguity
if r > 1 then
-- Assume all are 0-255
setRGB(self, r/255, g/255, b/255, a/255)
else
-- Assume all are 0-1
setRGB(self, r, g, b, a)
end
self.hex = sfutil.colorRGBToHex(self:UnpackRGB())
end
return self
end
--[[ ---------------------
Create a new ZO_ColorDef object with the same color values
as are in the SF_Color object.
color:ToZO_ColorDef()
Returns: ZO_ColorDef object.
--]]
function SF_Color:ToZO_ColorDef()
return ZO_ColorDef:New(self:UnpackRGBA())
end
--[[ ---------------------
wraps the ESO colorizing markup on a string of text for display
color:Colorize(text)
Parameter:
text - string text to be colorized,
text - number to use GetString() to get localized text to color,
text - nil to return "" (empty string)
Return:
string - colorized text (e.g., "|cFF0000Hello|r")
--]]
function SF_Color:Colorize(text)
local strprompt
if( text == nil ) then
return "" -- Do NOT colorize an empty string!
elseif( type(text) == "string") then
strprompt = text
elseif( type(text) == "number") then
strprompt = GetString(text)
else
strprompt = tostring(text)
end
--if self then
-- if self.rgb then d("r="..self.rgb.r.." g="..self.rgb.g.." b="..self.rgb.b) end
-- if self.hex then d("color to colorize = 0x"..self.hex) end
--end
local combineTable = { "|c", self.hex, strprompt, "|r" }
return table.concat(combineTable)
end
--[[ ---------------------
Compares two color objects.
color:IsEqual(other)
Parameter:
other - a ZO_ColorDef object, or
other - a SF_Color object
Logic: Compares RGB and Alpha values.
Returns: true if RGB and Alpha are equal, false otherwise.
--]]
function SF_Color:IsEqual(other)
if other.r ~= nil then
-- ZO_ColorDef
return self.rgb.r == other.r
and self.rgb.g == other.g
and self.rgb.b == other.b
and self.rgb.a == other.a
end
-- SF_Color
return self.hex == other.hex
and self.rgb.a == other.rgb.a
end
--[[
color:Clone()
Creates a deep copy of the color object.
Returns: New SF_Color instance with identical values.
--]]
function SF_Color:Clone()
return SF_Color:New(self:UnpackRGBA())
end
--[[
color:ToHex()
Returns the cached hex string.
Returns: String (e.g., "FF0000").
Note: This is the 6-character RGB hex. Alpha is not included in this specific string.
--]]
function SF_Color:ToHex()
return self.hex
end
sfutil.SF_Color = SF_Color