@quenty/color3utils 2.0.2-canary.256.edbbcfc.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,7 +3,18 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
- ## [2.0.2-canary.256.edbbcfc.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/color3utils@2.0.1...@quenty/color3utils@2.0.2-canary.256.edbbcfc.0) (2022-03-27)
6
+ # [3.0.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/color3utils@2.1.0...@quenty/color3utils@3.0.0) (2022-05-21)
7
+
8
+
9
+ ### Features
10
+
11
+ * Add LuvUtils to Color3Utils ([b705bdd](https://github.com/Quenty/NevermoreEngine/commit/b705bdd8cd1613329a73362899a4b8cd47f45336))
12
+
13
+
14
+
15
+
16
+
17
+ # [2.1.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/color3utils@2.0.1...@quenty/color3utils@2.1.0) (2022-03-27)
7
18
 
8
19
  **Note:** Version bump only for package @quenty/color3utils
9
20
 
package/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2014-2021 Quenty
3
+ Copyright (c) 2014-2022 Quenty
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quenty/color3utils",
3
- "version": "2.0.2-canary.256.edbbcfc.0",
3
+ "version": "3.0.0",
4
4
  "description": "Utility methods for Roblox Color3 values",
5
5
  "keywords": [
6
6
  "Roblox",
@@ -27,5 +27,12 @@
27
27
  "publishConfig": {
28
28
  "access": "public"
29
29
  },
30
- "gitHead": "edbbcfc38516772a791d50dc43cd6b304ffc4aff"
30
+ "dependencies": {
31
+ "@quenty/blend": "^4.0.0",
32
+ "@quenty/loader": "^5.0.0",
33
+ "@quenty/math": "^2.2.0",
34
+ "@quenty/rx": "^5.0.0",
35
+ "@quenty/valueobject": "^5.0.0"
36
+ },
37
+ "gitHead": "9f7eaea7543c33c89d2e32c38491b13f9271f4f7"
31
38
  }
@@ -0,0 +1,68 @@
1
+ --[=[
2
+ Handles color manipulation in the HpLuv space.
3
+
4
+ https://www.hsluv.org/comparison/
5
+
6
+ @class LuvColor3Utils
7
+ ]=]
8
+
9
+ local require = require(script.Parent.loader).load(script)
10
+
11
+ local LuvUtils = require("LuvUtils")
12
+ local Math = require("Math")
13
+
14
+ local LuvColor3Utils = {}
15
+
16
+ --[=[
17
+ Interpolates in LUV space.
18
+ @param color0 Color3
19
+ @param color1 Color3
20
+ @param t number
21
+ @return Color3
22
+ ]=]
23
+ function LuvColor3Utils.lerp(color0, color1, t)
24
+ assert(typeof(color0) == "Color3", "Bad color0")
25
+ assert(typeof(color1) == "Color3", "Bad color0")
26
+ assert(type(t) == "number", "Bad t")
27
+
28
+ if t == 0 then
29
+ return color0
30
+ elseif t == 1 then
31
+ return color1
32
+ else
33
+ local l0, u0, v0 = unpack(LuvColor3Utils.fromColor3(color0))
34
+ local l1, u1, v1 = unpack(LuvColor3Utils.fromColor3(color1))
35
+
36
+ local l = Math.lerp(l0, l1, t)
37
+ local u = Math.lerp(u0, u1, t)
38
+ local v = Math.lerp(v0, v1, t)
39
+
40
+ return LuvColor3Utils.toColor3({l, u, v})
41
+ end
42
+ end
43
+
44
+ --[=[
45
+ Converts from Color3 to LUV
46
+ @param color3 Color3
47
+ @return { number, number, number }
48
+ ]=]
49
+ function LuvColor3Utils.fromColor3(color3)
50
+ assert(typeof(color3) == "Color3", "Bad color3")
51
+
52
+ return LuvUtils.rgb_to_hsluv({ color3.r, color3.g, color3.b })
53
+ end
54
+
55
+ --[=[
56
+ Converts from LUV to Color3
57
+ @param luv { number, number, number }
58
+ @return Color3
59
+ ]=]
60
+ function LuvColor3Utils.toColor3(luv)
61
+ assert(type(luv) == "table", "Bad luv")
62
+
63
+ local r, g, b = unpack(LuvUtils.hsluv_to_rgb(luv))
64
+ -- deal with floating point numbers
65
+ return Color3.new(math.clamp(r, 0, 1), math.clamp(g, 0, 1), math.clamp(b, 0, 1))
66
+ end
67
+
68
+ return LuvColor3Utils
@@ -0,0 +1,348 @@
1
+ --[[
2
+ Lua implementation of LuvUtils and HPLuv color spaces
3
+ Homepage: http://www.LuvUtils.org/
4
+
5
+ Copyright (C) 2019 Alexei Boronine
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
8
+ associated documentation files (the "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
11
+ following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all copies or substantial
14
+ portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
17
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
18
+ NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+ ]]
22
+
23
+ -- https://github.com/hsluv/hsluv-lua
24
+
25
+ local LuvUtils = {}
26
+
27
+ local hexChars = "0123456789abcdef"
28
+
29
+ local function distance_line_from_origin(line)
30
+ return math.abs(line.intercept) / math.sqrt((line.slope*line.slope) + 1)
31
+ end
32
+
33
+ local function length_of_ray_until_intersect(theta, line)
34
+ return line.intercept / (math.sin(theta) - line.slope * math.cos(theta))
35
+ end
36
+
37
+ function LuvUtils.get_bounds(l)
38
+ local result = {}
39
+ local sub2
40
+ local sub1 = ((l + 16) ^ 3) / 1560896
41
+ if sub1 > LuvUtils.epsilon then
42
+ sub2 = sub1
43
+ else
44
+ sub2 = l / LuvUtils.kappa
45
+ end
46
+
47
+ for i = 1, 3 do
48
+ local m1 = LuvUtils.m[i][1]
49
+ local m2 = LuvUtils.m[i][2]
50
+ local m3 = LuvUtils.m[i][3]
51
+
52
+ for t = 0, 1 do
53
+ local top1 = (284517 * m1 - 94839 * m3) * sub2
54
+ local top2 = (838422 * m3 + 769860 * m2 + 731718 * m1) * l * sub2 - 769860 * t * l
55
+ local bottom = (632260 * m3 - 126452 * m2) * sub2 + 126452 * t
56
+ table.insert(result, {
57
+ slope = top1 / bottom,
58
+ intercept = top2 / bottom
59
+ })
60
+ end
61
+ end
62
+ return result
63
+ end
64
+
65
+ function LuvUtils.max_safe_chroma_for_l(l)
66
+ local bounds = LuvUtils.get_bounds(l)
67
+ local min = 1.7976931348623157e+308
68
+
69
+ for i = 1, 6 do
70
+ local length = distance_line_from_origin(bounds[i])
71
+ if length >= 0 then
72
+ min = math.min(min, length)
73
+ end
74
+ end
75
+ return min
76
+ end
77
+
78
+ function LuvUtils.max_safe_chroma_for_lh(l, h)
79
+ local hrad = h / 360 * math.pi * 2
80
+ local bounds = LuvUtils.get_bounds(l)
81
+ local min = 1.7976931348623157e+308
82
+
83
+ for i = 1, 6 do
84
+ local bound = bounds[i]
85
+ local length = length_of_ray_until_intersect(hrad, bound)
86
+ if length >= 0 then
87
+ min = math.min(min, length)
88
+ end
89
+ end
90
+ return min
91
+ end
92
+
93
+ function LuvUtils.dot_product(a, b)
94
+ local sum = 0
95
+ for i = 1, 3 do
96
+ sum = sum + a[i] * b[i]
97
+ end
98
+ return sum
99
+ end
100
+
101
+ function LuvUtils.from_linear(c)
102
+ if c <= 0.0031308 then
103
+ return 12.92 * c
104
+ else
105
+ return 1.055 * (c ^ 0.416666666666666685) - 0.055
106
+ end
107
+ end
108
+
109
+ function LuvUtils.to_linear(c)
110
+ if c > 0.04045 then
111
+ return ((c + 0.055) / 1.055) ^ 2.4
112
+ else
113
+ return c / 12.92
114
+ end
115
+ end
116
+
117
+ function LuvUtils.xyz_to_rgb(tuple)
118
+ return {
119
+ LuvUtils.from_linear(LuvUtils.dot_product(LuvUtils.m[1], tuple)),
120
+ LuvUtils.from_linear(LuvUtils.dot_product(LuvUtils.m[2], tuple)),
121
+ LuvUtils.from_linear(LuvUtils.dot_product(LuvUtils.m[3], tuple))
122
+ }
123
+ end
124
+
125
+ function LuvUtils.rgb_to_xyz(tuple)
126
+ local rgbl = {
127
+ LuvUtils.to_linear(tuple[1]),
128
+ LuvUtils.to_linear(tuple[2]),
129
+ LuvUtils.to_linear(tuple[3])
130
+ }
131
+ return {
132
+ LuvUtils.dot_product(LuvUtils.minv[1], rgbl),
133
+ LuvUtils.dot_product(LuvUtils.minv[2], rgbl),
134
+ LuvUtils.dot_product(LuvUtils.minv[3], rgbl)
135
+ }
136
+ end
137
+
138
+ function LuvUtils.y_to_l(Y)
139
+ if Y <= LuvUtils.epsilon then
140
+ return Y / LuvUtils.refY * LuvUtils.kappa
141
+ else
142
+ return 116 * ((Y / LuvUtils.refY) ^ 0.333333333333333315) - 16
143
+ end
144
+ end
145
+
146
+ function LuvUtils.l_to_y(L)
147
+ if L <= 8 then
148
+ return LuvUtils.refY * L / LuvUtils.kappa
149
+ else
150
+ return LuvUtils.refY * (((L + 16) / 116) ^ 3)
151
+ end
152
+ end
153
+
154
+ function LuvUtils.xyz_to_luv(tuple)
155
+ local X = tuple[1]
156
+ local Y = tuple[2]
157
+ local divider = X + 15 * Y + 3 * tuple[3]
158
+ local varU = 4 * X
159
+ local varV = 9 * Y
160
+ if divider ~= 0 then
161
+ varU = varU / divider
162
+ varV = varV / divider
163
+ else
164
+ varU = 0
165
+ varV = 0
166
+ end
167
+ local L = LuvUtils.y_to_l(Y)
168
+ if L == 0 then
169
+ return { 0, 0, 0 }
170
+ end
171
+ return { L, 13 * L * (varU - LuvUtils.refU), 13 * L * (varV - LuvUtils.refV) }
172
+ end
173
+
174
+ function LuvUtils.luv_to_xyz(tuple)
175
+ local L = tuple[1]
176
+ local U = tuple[2]
177
+ local V = tuple[3]
178
+ if L == 0 then
179
+ return { 0, 0, 0 }
180
+ end
181
+ local varU = U / (13 * L) + LuvUtils.refU
182
+ local varV = V / (13 * L) + LuvUtils.refV
183
+ local Y = LuvUtils.l_to_y(L)
184
+ local X = 0 - (9 * Y * varU) / ((((varU - 4) * varV) - varU * varV))
185
+ return { X, Y, (9 * Y - 15 * varV * Y - varV * X) / (3 * varV) }
186
+ end
187
+
188
+ function LuvUtils.luv_to_lch(tuple)
189
+ local L = tuple[1]
190
+ local U = tuple[2]
191
+ local V = tuple[3]
192
+ local C = math.sqrt(U * U + V * V)
193
+ local H
194
+ if C < 0.00000001 then
195
+ H = 0
196
+ else
197
+ H = math.atan2(V, U) * 180.0 / 3.1415926535897932
198
+ if H < 0 then
199
+ H = 360 + H
200
+ end
201
+ end
202
+ return { L, C, H }
203
+ end
204
+
205
+ function LuvUtils.lch_to_luv(tuple)
206
+ local L = tuple[1]
207
+ local C = tuple[2]
208
+ local Hrad = tuple[3] / 360.0 * 2 * math.pi
209
+ return { L, math.cos(Hrad) * C, math.sin(Hrad) * C }
210
+ end
211
+
212
+ function LuvUtils.hsluv_to_lch(tuple)
213
+ local H = tuple[1]
214
+ local S = tuple[2]
215
+ local L = tuple[3]
216
+ if L > 99.9999999 then
217
+ return { 100, 0, H }
218
+ end
219
+ if L < 0.00000001 then
220
+ return { 0, 0, H }
221
+ end
222
+ return { L, LuvUtils.max_safe_chroma_for_lh(L, H) / 100 * S, H }
223
+ end
224
+
225
+ function LuvUtils.lch_to_hsluv(tuple)
226
+ local L = tuple[1]
227
+ local C = tuple[2]
228
+ local H = tuple[3]
229
+ local max_chroma = LuvUtils.max_safe_chroma_for_lh(L, H)
230
+ if L > 99.9999999 then
231
+ return { H, 0, 100 }
232
+ end
233
+ if L < 0.00000001 then
234
+ return { H, 0, 0 }
235
+ end
236
+
237
+ return { H, C / max_chroma * 100, L }
238
+ end
239
+
240
+ function LuvUtils.hpluv_to_lch(tuple)
241
+ local H = tuple[1]
242
+ local S = tuple[2]
243
+ local L = tuple[3]
244
+ if L > 99.9999999 then
245
+ return { 100, 0, H }
246
+ end
247
+ if L < 0.00000001 then
248
+ return { 0, 0, H }
249
+ end
250
+ return { L, LuvUtils.max_safe_chroma_for_l(L) / 100 * S, H }
251
+ end
252
+
253
+ function LuvUtils.lch_to_hpluv(tuple)
254
+ local L = tuple[1]
255
+ local C = tuple[2]
256
+ local H = tuple[3]
257
+ if L > 99.9999999 then
258
+ return { H, 0, 100 }
259
+ end
260
+ if L < 0.00000001 then
261
+ return { H, 0, 0 }
262
+ end
263
+ return { H, C / LuvUtils.max_safe_chroma_for_l(L) * 100, L }
264
+ end
265
+
266
+ function LuvUtils.rgb_to_hex(tuple)
267
+ local h = "#"
268
+ for i = 1, 3 do
269
+ local c = math.floor(tuple[i] * 255 + 0.5)
270
+ local digit2 = math.fmod(c, 16)
271
+ local x = (c - digit2) / 16
272
+ local digit1 = math.floor(x)
273
+ h = h .. string.sub(hexChars, digit1 + 1, digit1 + 1)
274
+ h = h .. string.sub(hexChars, digit2 + 1, digit2 + 1)
275
+ end
276
+ return h
277
+ end
278
+
279
+ function LuvUtils.hex_to_rgb(hex)
280
+ hex = string.lower(hex)
281
+ local ret = {}
282
+ for i = 0, 2 do
283
+ local char1 = string.sub(hex, i * 2 + 2, i * 2 + 2)
284
+ local char2 = string.sub(hex, i * 2 + 3, i * 2 + 3)
285
+ local digit1 = string.find(hexChars, char1) - 1
286
+ local digit2 = string.find(hexChars, char2) - 1
287
+ ret[i + 1] = (digit1 * 16 + digit2) / 255.0
288
+ end
289
+ return ret
290
+ end
291
+
292
+ function LuvUtils.lch_to_rgb(tuple)
293
+ return LuvUtils.xyz_to_rgb(LuvUtils.luv_to_xyz(LuvUtils.lch_to_luv(tuple)))
294
+ end
295
+
296
+ function LuvUtils.rgb_to_lch(tuple)
297
+ return LuvUtils.luv_to_lch(LuvUtils.xyz_to_luv(LuvUtils.rgb_to_xyz(tuple)))
298
+ end
299
+
300
+ function LuvUtils.hsluv_to_rgb(tuple)
301
+ return LuvUtils.lch_to_rgb(LuvUtils.hsluv_to_lch(tuple))
302
+ end
303
+
304
+ function LuvUtils.rgb_to_hsluv(tuple)
305
+ return LuvUtils.lch_to_hsluv(LuvUtils.rgb_to_lch(tuple))
306
+ end
307
+
308
+ function LuvUtils.hpluv_to_rgb(tuple)
309
+ return LuvUtils.lch_to_rgb(LuvUtils.hpluv_to_lch(tuple))
310
+ end
311
+
312
+ function LuvUtils.rgb_to_hpluv(tuple)
313
+ return LuvUtils.lch_to_hpluv(LuvUtils.rgb_to_lch(tuple))
314
+ end
315
+
316
+ function LuvUtils.hsluv_to_hex(tuple)
317
+ return LuvUtils.rgb_to_hex(LuvUtils.hsluv_to_rgb(tuple))
318
+ end
319
+
320
+ function LuvUtils.hpluv_to_hex(tuple)
321
+ return LuvUtils.rgb_to_hex(LuvUtils.hpluv_to_rgb(tuple))
322
+ end
323
+
324
+ function LuvUtils.hex_to_hsluv(s)
325
+ return LuvUtils.rgb_to_hsluv(LuvUtils.hex_to_rgb(s))
326
+ end
327
+
328
+ function LuvUtils.hex_to_hpluv(s)
329
+ return LuvUtils.rgb_to_hpluv(LuvUtils.hex_to_rgb(s))
330
+ end
331
+
332
+ LuvUtils.m = {
333
+ { 3.240969941904521, -1.537383177570093, -0.498610760293 },
334
+ { -0.96924363628087, 1.87596750150772, 0.041555057407175 },
335
+ { 0.055630079696993, -0.20397695888897, 1.056971514242878 }
336
+ }
337
+ LuvUtils.minv = {
338
+ { 0.41239079926595, 0.35758433938387, 0.18048078840183 },
339
+ { 0.21263900587151, 0.71516867876775, 0.072192315360733 },
340
+ { 0.019330818715591, 0.11919477979462, 0.95053215224966 }
341
+ }
342
+ LuvUtils.refY = 1.0
343
+ LuvUtils.refU = 0.19783000664283
344
+ LuvUtils.refV = 0.46831999493879
345
+ LuvUtils.kappa = 903.2962962
346
+ LuvUtils.epsilon = 0.0088564516
347
+
348
+ return LuvUtils
@@ -0,0 +1,41 @@
1
+ --[=[
2
+ @class LuvVectorColor3Utils
3
+ ]=]
4
+
5
+ local require = require(script.Parent.loader).load(script)
6
+
7
+ local LuvUtils = require("LuvUtils")
8
+
9
+ local LuvVectorColor3Utils = {}
10
+
11
+ --[=[
12
+ Converts the Color3 to a Vector3 which can be interpolated by something
13
+ like a spring.
14
+ @param color3 Color3
15
+ @return Vector3
16
+ ]=]
17
+ function LuvVectorColor3Utils.fromColor3(color3)
18
+ local hsl = LuvUtils.rgb_to_hsluv({ color3.r, color3.g, color3.b })
19
+
20
+ -- Transform from -100% to 100% into 0 to 1 space
21
+ return Vector3.new(
22
+ hsl[1]/200 + 0.5,
23
+ hsl[2]/200 + 0.5,
24
+ hsl[3]/200 + 0.5)
25
+ end
26
+
27
+ --[=[
28
+ Converts the Vector3 to a Color3 assuming it is the interpolated version.
29
+ @param luvV3 Vector3
30
+ @return Color3
31
+ ]=]
32
+ function LuvVectorColor3Utils.toColor3(luvV3)
33
+ -- Transform out of this space
34
+ return Color3.new(unpack(LuvUtils.hsluv_to_rgb({
35
+ (luvV3.x - 0.5)*200,
36
+ (luvV3.y - 0.5)*200,
37
+ (luvV3.z - 0.5)*200})
38
+ ))
39
+ end
40
+
41
+ return LuvVectorColor3Utils
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "node_modules",
3
+ "globIgnorePaths": [ "**/.package-lock.json" ],
4
+ "tree": {
5
+ "$path": { "optional": "../node_modules" }
6
+ }
7
+ }