···11--- Taken and slightly modified without shame from https://github.com/hsluv/hsluv-lua/blob/master/hsluv.lua
22-33---[[
44-Lua implementation of HSLuv and HPLuv color spaces
55-Homepage: http://www.hsluv.org/
66-77-Copyright (C) 2019 Alexei Boronine
88-99-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
1010-associated documentation files (the "Software"), to deal in the Software without restriction, including
1111-without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1212-copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
1313-following conditions:
1414-1515-The above copyright notice and this permission notice shall be included in all copies or substantial
1616-portions of the Software.
1717-1818-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
1919-LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
2020-NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
2121-WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
2222-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2323-]]
2424-2525----@class hsluv.Line
2626----@field slope number
2727----@field intercept number
2828-2929-local M = {}
3030-3131-local hexChars = "0123456789abcdef"
3232-3333----@param line hsluv.Line
3434----@return number
3535-local function distance_line_from_origin(line)
3636- return math.abs(line.intercept) / math.sqrt((line.slope ^ 2) + 1)
3737-end
3838-3939----@param theta number
4040----@param line hsluv.Line
4141----@return number
4242-local function length_of_ray_until_intersect(theta, line)
4343- return line.intercept / (math.sin(theta) - line.slope * math.cos(theta))
4444-end
4545-4646----@param l number
4747----@return hsluv.Line[]
4848-function M.get_bounds(l)
4949- local result = {}
5050- local sub2
5151- local sub1 = ((l + 16) ^ 3) / 1560896
5252- if sub1 > M.epsilon then
5353- sub2 = sub1
5454- else
5555- sub2 = l / M.kappa
5656- end
5757-5858- for i = 1, 3 do
5959- local m1 = M.m[i][1]
6060- local m2 = M.m[i][2]
6161- local m3 = M.m[i][3]
6262-6363- for t = 0, 1 do
6464- local top1 = (284517 * m1 - 94839 * m3) * sub2
6565- local top2 = (838422 * m3 + 769860 * m2 + 731718 * m1) * l * sub2 - 769860 * t * l
6666- local bottom = (632260 * m3 - 126452 * m2) * sub2 + 126452 * t
6767- table.insert(result, {
6868- slope = top1 / bottom,
6969- intercept = top2 / bottom,
7070- })
7171- end
7272- end
7373- return result
7474-end
7575-7676----@param l number
7777----@return number
7878-function M.max_safe_chroma_for_l(l)
7979- local bounds = M.get_bounds(l)
8080- local min = 1.7976931348623157e+308
8181-8282- for i = 1, 6 do
8383- local length = distance_line_from_origin(bounds[i])
8484- if length >= 0 then min = math.min(min, length) end
8585- end
8686- return min
8787-end
8888-8989----@param l number
9090----@param h number
9191----@return number
9292-function M.max_safe_chroma_for_lh(l, h)
9393- local hrad = h / 360 * math.pi * 2
9494- local bounds = M.get_bounds(l)
9595- local min = 1.7976931348623157e+308
9696-9797- for i = 1, 6 do
9898- local bound = bounds[i]
9999- local length = length_of_ray_until_intersect(hrad, bound)
100100- if length >= 0 then min = math.min(min, length) end
101101- end
102102- return min
103103-end
104104-105105----@param a number[]
106106----@param b number[]
107107----@return number
108108-function M.dot_product(a, b)
109109- local sum = 0
110110- for i = 1, 3 do
111111- sum = sum + a[i] * b[i]
112112- end
113113- return sum
114114-end
115115-116116----@param c number
117117----@return number
118118-function M.from_linear(c)
119119- if c <= 0.0031308 then
120120- return 12.92 * c
121121- else
122122- return 1.055 * (c ^ 0.416666666666666685) - 0.055
123123- end
124124-end
125125-126126----@param c number
127127----@return number
128128-function M.to_linear(c)
129129- if c > 0.04045 then
130130- return ((c + 0.055) / 1.055) ^ 2.4
131131- else
132132- return c / 12.92
133133- end
134134-end
135135-136136----@param tuple number[]
137137----@return number[]
138138-function M.xyz_to_rgb(tuple)
139139- return {
140140- M.from_linear(M.dot_product(M.m[1], tuple)),
141141- M.from_linear(M.dot_product(M.m[2], tuple)),
142142- M.from_linear(M.dot_product(M.m[3], tuple)),
143143- }
144144-end
145145-146146----@param tuple number[]
147147----@return number[]
148148-function M.rgb_to_xyz(tuple)
149149- local rgbl = {
150150- M.to_linear(tuple[1]),
151151- M.to_linear(tuple[2]),
152152- M.to_linear(tuple[3]),
153153- }
154154- return {
155155- M.dot_product(M.minv[1], rgbl),
156156- M.dot_product(M.minv[2], rgbl),
157157- M.dot_product(M.minv[3], rgbl),
158158- }
159159-end
160160-161161----@param Y number
162162----@return number
163163-function M.y_to_l(Y)
164164- if Y <= M.epsilon then
165165- return Y / M.refY * M.kappa
166166- else
167167- return 116 * ((Y / M.refY) ^ 0.333333333333333315) - 16
168168- end
169169-end
170170-171171----@param L number
172172----@return number
173173-function M.l_to_y(L)
174174- if L <= 8 then
175175- return M.refY * L / M.kappa
176176- else
177177- return M.refY * (((L + 16) / 116) ^ 3)
178178- end
179179-end
180180-181181----@param tuple number[]
182182----@return number[]
183183-function M.xyz_to_luv(tuple)
184184- local X = tuple[1]
185185- local Y = tuple[2]
186186- local divider = X + 15 * Y + 3 * tuple[3]
187187- local varU = 4 * X
188188- local varV = 9 * Y
189189- if divider ~= 0 then
190190- varU = varU / divider
191191- varV = varV / divider
192192- else
193193- varU = 0
194194- varV = 0
195195- end
196196- local L = M.y_to_l(Y)
197197- if L == 0 then return { 0, 0, 0 } end
198198- return { L, 13 * L * (varU - M.refU), 13 * L * (varV - M.refV) }
199199-end
200200-201201----@param tuple number[]
202202----@return number[]
203203-function M.luv_to_xyz(tuple)
204204- local L = tuple[1]
205205- local U = tuple[2]
206206- local V = tuple[3]
207207- if L == 0 then return { 0, 0, 0 } end
208208- local varU = U / (13 * L) + M.refU
209209- local varV = V / (13 * L) + M.refV
210210- local Y = M.l_to_y(L)
211211- local X = 0 - (9 * Y * varU) / (((varU - 4) * varV) - varU * varV)
212212- return { X, Y, (9 * Y - 15 * varV * Y - varV * X) / (3 * varV) }
213213-end
214214-215215----@param tuple number[]
216216----@return number[]
217217-function M.luv_to_lch(tuple)
218218- local L = tuple[1]
219219- local U = tuple[2]
220220- local V = tuple[3]
221221- local C = math.sqrt(U * U + V * V)
222222- local H
223223- if C < 0.00000001 then
224224- H = 0
225225- else
226226- H = math.atan2(V, U) * 180.0 / 3.1415926535897932
227227- if H < 0 then H = 360 + H end
228228- end
229229- return { L, C, H }
230230-end
231231-232232----@param tuple number[]
233233----@return number[]
234234-function M.lch_to_luv(tuple)
235235- local L = tuple[1]
236236- local C = tuple[2]
237237- local Hrad = tuple[3] / 360.0 * 2 * math.pi
238238- return { L, math.cos(Hrad) * C, math.sin(Hrad) * C }
239239-end
240240-241241----@param tuple number[]
242242----@return number[]
243243-function M.hsluv_to_lch(tuple)
244244- local H = tuple[1]
245245- local S = tuple[2]
246246- local L = tuple[3]
247247- if L > 99.9999999 then return { 100, 0, H } end
248248- if L < 0.00000001 then return { 0, 0, H } end
249249- return { L, M.max_safe_chroma_for_lh(L, H) / 100 * S, H }
250250-end
251251-252252----@param tuple number[]
253253----@return number[]
254254-function M.lch_to_hsluv(tuple)
255255- local L = tuple[1]
256256- local C = tuple[2]
257257- local H = tuple[3]
258258- local max_chroma = M.max_safe_chroma_for_lh(L, H)
259259- if L > 99.9999999 then return { H, 0, 100 } end
260260- if L < 0.00000001 then return { H, 0, 0 } end
261261-262262- return { H, C / max_chroma * 100, L }
263263-end
264264-265265----@param tuple number[]
266266----@return number[]
267267-function M.hpluv_to_lch(tuple)
268268- local H = tuple[1]
269269- local S = tuple[2]
270270- local L = tuple[3]
271271- if L > 99.9999999 then return { 100, 0, H } end
272272- if L < 0.00000001 then return { 0, 0, H } end
273273- return { L, M.max_safe_chroma_for_l(L) / 100 * S, H }
274274-end
275275-276276----@param tuple number[]
277277----@return number[]
278278-function M.lch_to_hpluv(tuple)
279279- local L = tuple[1]
280280- local C = tuple[2]
281281- local H = tuple[3]
282282- if L > 99.9999999 then return { H, 0, 100 } end
283283- if L < 0.00000001 then return { H, 0, 0 } end
284284- return { H, C / M.max_safe_chroma_for_l(L) * 100, L }
285285-end
286286-287287----@param tuple number[]
288288----@return string
289289-function M.rgb_to_hex(tuple)
290290- local h = "#"
291291- for i = 1, 3 do
292292- local c = math.floor(tuple[i] * 255 + 0.5)
293293- local digit2 = math.fmod(c, 16)
294294- local x = (c - digit2) / 16
295295- local digit1 = math.floor(x)
296296- h = h .. string.sub(hexChars, digit1 + 1, digit1 + 1)
297297- h = h .. string.sub(hexChars, digit2 + 1, digit2 + 1)
298298- end
299299- return h
300300-end
301301-302302----@param hex string
303303----@return number[]
304304-function M.hex_to_rgb(hex)
305305- hex = string.lower(hex)
306306- local ret = {}
307307- for i = 0, 2 do
308308- local char1 = string.sub(hex, i * 2 + 2, i * 2 + 2)
309309- local char2 = string.sub(hex, i * 2 + 3, i * 2 + 3)
310310- local digit1 = string.find(hexChars, char1) - 1
311311- local digit2 = string.find(hexChars, char2) - 1
312312- ret[i + 1] = (digit1 * 16 + digit2) / 255.0
313313- end
314314- return ret
315315-end
316316-317317----@param tuple number[]
318318----@return number[]
319319-function M.lch_to_rgb(tuple) return M.xyz_to_rgb(M.luv_to_xyz(M.lch_to_luv(tuple))) end
320320-321321----@param tuple number[]
322322----@return number[]
323323-function M.rgb_to_lch(tuple) return M.luv_to_lch(M.xyz_to_luv(M.rgb_to_xyz(tuple))) end
324324-325325----@param tuple number[]
326326----@return number[]
327327-function M.hsluv_to_rgb(tuple) return M.lch_to_rgb(M.hsluv_to_lch(tuple)) end
328328-329329----@param tuple number[]
330330----@return number[]
331331-function M.rgb_to_hsluv(tuple) return M.lch_to_hsluv(M.rgb_to_lch(tuple)) end
332332-333333----@param tuple number[]
334334----@return number[]
335335-function M.hpluv_to_rgb(tuple) return M.lch_to_rgb(M.hpluv_to_lch(tuple)) end
336336-337337----@param tuple number[]
338338----@return number[]
339339-function M.rgb_to_hpluv(tuple) return M.lch_to_hpluv(M.rgb_to_lch(tuple)) end
340340-341341----@param tuple number[]
342342----@return string
343343-function M.hsluv_to_hex(tuple) return M.rgb_to_hex(M.hsluv_to_rgb(tuple)) end
344344-345345----@param tuple number[]
346346----@return string
347347-function M.hpluv_to_hex(tuple) return M.rgb_to_hex(M.hpluv_to_rgb(tuple)) end
348348-349349----@param s string
350350----@return number[]
351351-function M.hex_to_hsluv(s) return M.rgb_to_hsluv(M.hex_to_rgb(s)) end
352352-353353----@param s string
354354----@return number[]
355355-function M.hex_to_hpluv(s) return M.rgb_to_hpluv(M.hex_to_rgb(s)) end
356356-357357----@type table<number, table<number, number>>
358358-M.m = {
359359- { 3.240969941904521, -1.537383177570093, -0.498610760293 },
360360- { -0.96924363628087, 1.87596750150772, 0.041555057407175 },
361361- { 0.055630079696993, -0.20397695888897, 1.056971514242878 },
362362-}
363363-364364----@type table<number, table<number, number>>
365365-M.minv = {
366366- { 0.41239079926595, 0.35758433938387, 0.18048078840183 },
367367- { 0.21263900587151, 0.71516867876775, 0.072192315360733 },
368368- { 0.019330818715591, 0.11919477979462, 0.95053215224966 },
369369-}
370370-371371-M.refY = 1.0
372372-M.refU = 0.19783000664283
373373-M.refV = 0.46831999493879
374374-M.kappa = 903.2962962
375375-M.epsilon = 0.0088564516
376376-377377-return M