• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

excessive / cpml / 6132588926

09 Sep 2023 06:38PM UTC coverage: 14.013% (-44.7%) from 58.701%
6132588926

push

github

FatalError42O
fixed Busted support (hopefully)

975 of 6958 relevant lines covered (14.01%)

10.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.57
/modules/color.lua
1
--- Color utilities
2
-- @module color
3

4
local constants = require(modules .. "constants")
3✔
5
local utils    = require(modules .. "utils")
×
6
local precond  = require(modules .. "_private_precond")
×
7
local color    = {}
×
8
local color_mt = {}
×
9

10
local function new(r, g, b, a)
11
        local c = { r, g, b, a }
×
12
        c._c = c
×
13
        return setmetatable(c, color_mt)
×
14
end
15

16
-- HSV utilities (adapted from http://www.cs.rit.edu/~ncs/color/t_convert.html)
17
-- hsv_to_color(hsv)
18
-- Converts a set of HSV values to a color. hsv is a table.
19
-- See also: hsv(h, s, v)
20
local function hsv_to_color(hsv)
21
        local i
22
        local f, q, p, t
23
        local h, s, v
24
        local a = hsv[4] or 1
×
25
        s = hsv[2]
×
26
        v = hsv[3]
×
27

28
        if s == 0 then
×
29
                return new(v, v, v, a)
×
30
        end
31

32
        h = hsv[1] * 6 -- sector 0 to 5
×
33

34
        i = math.floor(h)
×
35
        f = h - i -- factorial part of h
×
36
        p = v * (1-s)
×
37
        q = v * (1-s*f)
×
38
        t = v * (1-s*(1-f))
×
39

40
        if     i == 0 then return new(v, t, p, a)
×
41
        elseif i == 1 then return new(q, v, p, a)
×
42
        elseif i == 2 then return new(p, v, t, a)
×
43
        elseif i == 3 then return new(p, q, v, a)
×
44
        elseif i == 4 then return new(t, p, v, a)
×
45
        else               return new(v, p, q, a)
×
46
        end
47
end
48

49
-- color_to_hsv(c)
50
-- Takes in a normal color and returns a table with the HSV values.
51
local function color_to_hsv(c)
52
        local r = c[1]
×
53
        local g = c[2]
×
54
        local b = c[3]
×
55
        local a = c[4] or 1
×
56
        local h, s, v
57

58
        local min = math.min(r, g, b)
×
59
        local max = math.max(r, g, b)
×
60
        v = max
×
61

62
        local delta = max - min
×
63

64
        -- black, nothing else is really possible here.
65
        if min == 0 and max == 0 then
×
66
                return { 0, 0, 0, a }
×
67
        end
68

69
        if max ~= 0 then
×
70
                s = delta / max
×
71
        else
72
                -- r = g = b = 0 s = 0, v is undefined
73
                s = 0
×
74
                h = -1
×
75
                return { h, s, v, 1 }
×
76
        end
77

78
        -- Prevent division by zero.
79
        if delta == 0 then
×
80
                delta = constants.DBL_EPSILON
×
81
        end
82

83
        if r == max then
×
84
                h = ( g - b ) / delta     -- yellow/magenta
×
85
        elseif g == max then
×
86
                h = 2 + ( b - r ) / delta -- cyan/yellow
×
87
        else
88
                h = 4 + ( r - g ) / delta -- magenta/cyan
×
89
        end
90

91
        h = h / 6 -- normalize from segment 0..5
×
92

93
        if h < 0 then
×
94
                h = h + 1
×
95
        end
96

97
        return { h, s, v, a }
×
98
end
99

100
--- The public constructor.
101
-- @param x Can be of three types: </br>
102
-- number red component 0-1
103
-- table {r, g, b, a}
104
-- nil for {0,0,0,0}
105
-- @tparam number g Green component 0-1
106
-- @tparam number b Blue component 0-1
107
-- @tparam number a Alpha component 0-1
108
-- @treturn color out
109
function color.new(r, g, b, a)
×
110
        -- number, number, number, number
111
        if r and g and b and a then
×
112
                precond.typeof(r, "number", "new: Wrong argument type for r")
×
113
                precond.typeof(g, "number", "new: Wrong argument type for g")
×
114
                precond.typeof(b, "number", "new: Wrong argument type for b")
×
115
                precond.typeof(a, "number", "new: Wrong argument type for a")
×
116

117
                return new(r, g, b, a)
×
118

119
        -- {r, g, b, a}
120
        elseif type(r) == "table" then
×
121
                local rr, gg, bb, aa = r[1], r[2], r[3], r[4]
×
122
                precond.typeof(rr, "number", "new: Wrong argument type for r")
×
123
                precond.typeof(gg, "number", "new: Wrong argument type for g")
×
124
                precond.typeof(bb, "number", "new: Wrong argument type for b")
×
125
                precond.typeof(aa, "number", "new: Wrong argument type for a")
×
126

127
                return new(rr, gg, bb, aa)
×
128
        end
129

130
        return new(0, 0, 0, 0)
×
131
end
132

133
--- Convert hue,saturation,value table to color object.
134
-- @tparam table hsva {hue 0-1, saturation 0-1, value 0-1, alpha 0-1}
135
-- @treturn color out
136
color.hsv_to_color_table = hsv_to_color
×
137

138
--- Convert color to hue,saturation,value table
139
-- @tparam color in
140
-- @treturn table hsva {hue 0-1, saturation 0-1, value 0-1, alpha 0-1}
141
color.color_to_hsv_table = color_to_hsv
×
142

143
--- Convert hue,saturation,value to color object.
144
-- @tparam number h hue 0-1
145
-- @tparam number s saturation 0-1
146
-- @tparam number v value 0-1
147
-- @treturn color out
148
function color.from_hsv(h, s, v)
×
149
        return hsv_to_color { h, s, v }
×
150
end
151

152
--- Convert hue,saturation,value to color object.
153
-- @tparam number h hue 0-1
154
-- @tparam number s saturation 0-1
155
-- @tparam number v value 0-1
156
-- @tparam number a alpha 0-1
157
-- @treturn color out
158
function color.from_hsva(h, s, v, a)
×
159
        return hsv_to_color { h, s, v, a }
×
160
end
161

162
--- Invert a color.
163
-- @tparam color to invert
164
-- @treturn color out
165
function color.invert(c)
×
166
        return new(1 - c[1], 1 - c[2], 1 - c[3], c[4])
×
167
end
168

169
--- Lighten a color by a component-wise fixed amount (alpha unchanged)
170
-- @tparam color to lighten
171
-- @tparam number amount to increase each component by, 0-1 scale
172
-- @treturn color out
173
function color.lighten(c, v)
×
174
        return new(
×
175
                utils.clamp(c[1] + v, 0, 1),
×
176
                utils.clamp(c[2] + v, 0, 1),
×
177
                utils.clamp(c[3] + v, 0, 1),
×
178
                c[4]
179
        )
180
end
181

182
--- Interpolate between two colors.
183
-- @tparam color at start
184
-- @tparam color at end
185
-- @tparam number s in 0-1 progress between the two colors
186
-- @treturn color out
187
function color.lerp(a, b, s)
×
188
        return a + s * (b - a)
×
189
end
190

191
--- Unpack a color into individual components in 0-1.
192
-- @tparam color to unpack
193
-- @treturn number r in 0-1
194
-- @treturn number g in 0-1
195
-- @treturn number b in 0-1
196
-- @treturn number a in 0-1
197
function color.unpack(c)
×
198
        return c[1], c[2], c[3], c[4]
×
199
end
200

201
--- Unpack a color into individual components in 0-255.
202
-- @tparam color to unpack
203
-- @treturn number r in 0-255
204
-- @treturn number g in 0-255
205
-- @treturn number b in 0-255
206
-- @treturn number a in 0-255
207
function color.as_255(c)
×
208
        return c[1] * 255, c[2] * 255, c[3] * 255, c[4] * 255
×
209
end
210

211
--- Darken a color by a component-wise fixed amount (alpha unchanged)
212
-- @tparam color to darken
213
-- @tparam number amount to decrease each component by, 0-1 scale
214
-- @treturn color out
215
function color.darken(c, v)
×
216
        return new(
×
217
                utils.clamp(c[1] - v, 0, 1),
×
218
                utils.clamp(c[2] - v, 0, 1),
×
219
                utils.clamp(c[3] - v, 0, 1),
×
220
                c[4]
221
        )
222
end
223

224
--- Multiply a color's components by a value (alpha unchanged)
225
-- @tparam color to multiply
226
-- @tparam number to multiply each component by
227
-- @treturn color out
228
function color.multiply(c, v)
×
229
        local t = color.new()
×
230
        for i = 1, 3 do
×
231
                t[i] = c[i] * v
×
232
        end
233

234
        t[4] = c[4]
×
235
        return t
×
236
end
237

238
-- directly set alpha channel
239
-- @tparam color to alter
240
-- @tparam number new alpha 0-1
241
-- @treturn color out
242
function color.alpha(c, v)
×
243
        local t = color.new()
×
244
        for i = 1, 3 do
×
245
                t[i] = c[i]
×
246
        end
247

248
        t[4] = v
×
249
        return t
×
250
end
251

252
--- Multiply a color's alpha by a value
253
-- @tparam color to multiply
254
-- @tparam number to multiply alpha by
255
-- @treturn color out
256
function color.opacity(c, v)
×
257
        local t = color.new()
×
258
        for i = 1, 3 do
×
259
                t[i] = c[i]
×
260
        end
261

262
        t[4] = c[4] * v
×
263
        return t
×
264
end
265

266
--- Set a color's hue (saturation, value, alpha unchanged)
267
-- @tparam color to alter
268
-- @tparam hue to set 0-1
269
-- @treturn color out
270
function color.hue(col, hue)
×
271
        local c = color_to_hsv(col)
×
272
        c[1] = (hue + 1) % 1
×
273
        return hsv_to_color(c)
×
274
end
275

276
--- Set a color's saturation (hue, value, alpha unchanged)
277
-- @tparam color to alter
278
-- @tparam saturation to set 0-1
279
-- @treturn color out
280
function color.saturation(col, percent)
×
281
        local c = color_to_hsv(col)
×
282
        c[2] = utils.clamp(percent, 0, 1)
×
283
        return hsv_to_color(c)
×
284
end
285

286
--- Set a color's value (saturation, hue, alpha unchanged)
287
-- @tparam color to alter
288
-- @tparam value to set 0-1
289
-- @treturn color out
290
function color.value(col, percent)
×
291
        local c = color_to_hsv(col)
×
292
        c[3] = utils.clamp(percent, 0, 1)
×
293
        return hsv_to_color(c)
×
294
end
295

296
-- https://en.wikipedia.org/wiki/SRGB#From_sRGB_to_CIE_XYZ
297
function color.gamma_to_linear(r, g, b, a)
×
298
        local function convert(c)
299
                if c > 1.0 then
×
300
                        return 1.0
×
301
                elseif c < 0.0 then
×
302
                        return 0.0
×
303
                elseif c <= 0.04045 then
×
304
                        return c / 12.92
×
305
                else
306
                        return math.pow((c + 0.055) / 1.055, 2.4)
×
307
                end
308
        end
309

310
        if type(r) == "table" then
×
311
                local c = {}
×
312
                for i = 1, 3 do
×
313
                        c[i] = convert(r[i])
×
314
                end
315

316
                c[4] = r[4]
×
317
                return c
×
318
        else
319
                return convert(r), convert(g), convert(b), a or 1
×
320
        end
321
end
322

323
-- https://en.wikipedia.org/wiki/SRGB#From_CIE_XYZ_to_sRGB
324
function color.linear_to_gamma(r, g, b, a)
×
325
        local function convert(c)
326
                if c > 1.0 then
×
327
                        return 1.0
×
328
                elseif c < 0.0 then
×
329
                        return 0.0
×
330
                elseif c < 0.0031308 then
×
331
                        return c * 12.92
×
332
                else
333
                        return 1.055 * math.pow(c, 0.41666) - 0.055
×
334
                end
335
        end
336

337
        if type(r) == "table" then
×
338
                local c = {}
×
339
                for i = 1, 3 do
×
340
                        c[i] = convert(r[i])
×
341
                end
342

343
                c[4] = r[4]
×
344
                return c
×
345
        else
346
                return convert(r), convert(g), convert(b), a or 1
×
347
        end
348
end
349

350
--- Check if color is valid
351
-- @tparam color to test
352
-- @treturn boolean is color
353
function color.is_color(a)
×
354
        if type(a) ~= "table" then
×
355
                return false
×
356
        end
357

358
        for i = 1, 4 do
×
359
                if type(a[i]) ~= "number" then
×
360
                        return false
×
361
                end
362
        end
363

364
        return true
×
365
end
366

367
--- Return a formatted string.
368
-- @tparam color a color to be turned into a string
369
-- @treturn string formatted
370
function color.to_string(a)
×
371
        return string.format("[ %3.0f, %3.0f, %3.0f, %3.0f ]", a[1], a[2], a[3], a[4])
×
372
end
373

374
color_mt.__index = color
×
375
color_mt.__tostring = color.to_string
×
376

377
function color_mt.__call(_, r, g, b, a)
×
378
        return color.new(r, g, b, a)
×
379
end
380

381
function color_mt.__add(a, b)
×
382
        return new(a[1] + b[1], a[2] + b[2], a[3] + b[3], a[4] + b[4])
×
383
end
384

385
function color_mt.__sub(a, b)
×
386
        return new(a[1] - b[1], a[2] - b[2], a[3] - b[3], a[4] - b[4])
×
387
end
388

389
function color_mt.__mul(a, b)
×
390
        if type(a) == "number" then
×
391
                return new(a * b[1], a * b[2], a * b[3], a * b[4])
×
392
        elseif type(b) == "number" then
×
393
                return new(b * a[1], b * a[2], b * a[3], b * a[4])
×
394
        else
395
                return new(a[1] * b[1], a[2] * b[2], a[3] * b[3], a[4] * b[4])
×
396
        end
397
end
398

399
return setmetatable({}, color_mt)
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc