From 48aa74743b3d7cc73f18b8ef32353629cf8dd6a2 Mon Sep 17 00:00:00 2001 From: ubq323 Date: Thu, 4 Jan 2024 18:57:31 +0000 Subject: initial --- hsluv.lua | 347 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 hsluv.lua (limited to 'hsluv.lua') diff --git a/hsluv.lua b/hsluv.lua new file mode 100644 index 0000000..1ff199a --- /dev/null +++ b/hsluv.lua @@ -0,0 +1,347 @@ +--[[ +Lua implementation of HSLuv and HPLuv color spaces +Homepage: http://www.hsluv.org/ + +Copyright (C) 2019 Alexei Boronine + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +]] + +hsluv = {} + +hexChars = "0123456789abcdef" + + +distance_line_from_origin = function(line) + return math.abs(line.intercept) / math.sqrt((line.slope ^ 2) + 1) +end + +length_of_ray_until_intersect = function(theta, line) + return line.intercept / (math.sin(theta) - line.slope * math.cos(theta)) +end + +hsluv.get_bounds = function(l) + local result = {}; + local sub2; + local sub1 = ((l + 16) ^ 3) / 1560896; + if sub1 > hsluv.epsilon then + sub2 = sub1; + else + sub2 = l / hsluv.kappa; + end + + for i = 1, 3 do + local m1 = hsluv.m[i][1]; + local m2 = hsluv.m[i][2]; + local m3 = hsluv.m[i][3]; + + for t = 0, 1 do + local top1 = (284517 * m1 - 94839 * m3) * sub2; + local top2 = (838422 * m3 + 769860 * m2 + 731718 * m1) * l * sub2 - 769860 * t * l; + local bottom = (632260 * m3 - 126452 * m2) * sub2 + 126452 * t; + table.insert(result, { + slope = top1 / bottom, + intercept = top2 / bottom + }) + end; + end; + return result +end + +hsluv.max_safe_chroma_for_l = function(l) + local bounds = hsluv.get_bounds(l); + local min = 1.7976931348623157e+308; + + for i = 1, 6 do + local length = distance_line_from_origin(bounds[i]); + if length >= 0 then + min = math.min(min, length); + end; + end; + return min; +end + +hsluv.max_safe_chroma_for_lh = function(l, h) + local hrad = h / 360 * math.pi * 2; + local bounds = hsluv.get_bounds(l); + local min = 1.7976931348623157e+308; + + for i = 1, 6 do + local bound = bounds[i]; + local length = length_of_ray_until_intersect(hrad, bound); + if length >= 0 then + min = math.min(min, length); + end; + end; + return min +end + +hsluv.dot_product = function(a, b) + local sum = 0; + for i = 1, 3 do + sum = sum + a[i] * b[i]; + end; + return sum +end + +hsluv.from_linear = function(c) + if c <= 0.0031308 then + return 12.92 * c + else + return 1.055 * (c ^ 0.416666666666666685) - 0.055 + end; +end + +hsluv.to_linear = function(c) + if c > 0.04045 then + return ((c + 0.055) / 1.055) ^ 2.4 + else + return c / 12.92 + end; +end + +hsluv.xyz_to_rgb = function(tuple) + return { + hsluv.from_linear(hsluv.dot_product(hsluv.m[1], tuple)), + hsluv.from_linear(hsluv.dot_product(hsluv.m[2], tuple)), + hsluv.from_linear(hsluv.dot_product(hsluv.m[3], tuple)) + } +end + +hsluv.rgb_to_xyz = function(tuple) + local rgbl = { + hsluv.to_linear(tuple[1]), + hsluv.to_linear(tuple[2]), + hsluv.to_linear(tuple[3]) + }; + return { + hsluv.dot_product(hsluv.minv[1], rgbl), + hsluv.dot_product(hsluv.minv[2], rgbl), + hsluv.dot_product(hsluv.minv[3], rgbl) + } +end + +hsluv.y_to_l = function(Y) + if Y <= hsluv.epsilon then + return Y / hsluv.refY * hsluv.kappa + else + return 116 * ((Y / hsluv.refY) ^ 0.333333333333333315) - 16 + end; +end + +hsluv.l_to_y = function(L) + if L <= 8 then + return hsluv.refY * L / hsluv.kappa + else + return hsluv.refY * (((L + 16) / 116) ^ 3) + end; +end + +hsluv.xyz_to_luv = function(tuple) + local X = tuple[1]; + local Y = tuple[2]; + local divider = X + 15 * Y + 3 * tuple[3]; + local varU = 4 * X; + local varV = 9 * Y; + if divider ~= 0 then + varU = varU / divider; + varV = varV / divider; + else + varU = 0; + varV = 0; + end + local L = hsluv.y_to_l(Y); + if L == 0 then + return { 0, 0, 0 } + end; + return { L, 13 * L * (varU - hsluv.refU), 13 * L * (varV - hsluv.refV) } +end + +hsluv.luv_to_xyz = function(tuple) + local L = tuple[1]; + local U = tuple[2]; + local V = tuple[3]; + if L == 0 then + return { 0, 0, 0 } + end; + local varU = U / (13 * L) + hsluv.refU; + local varV = V / (13 * L) + hsluv.refV; + local Y = hsluv.l_to_y(L); + local X = 0 - (9 * Y * varU) / ((((varU - 4) * varV) - varU * varV)); + return { X, Y, (9 * Y - 15 * varV * Y - varV * X) / (3 * varV) } +end + +hsluv.luv_to_lch = function(tuple) + local L = tuple[1]; + local U = tuple[2]; + local V = tuple[3]; + local C = math.sqrt(U * U + V * V); + local H + if C < 0.00000001 then + H = 0; + else + H = math.atan2(V, U) * 180.0 / 3.1415926535897932; + if H < 0 then + H = 360 + H; + end; + end; + return { L, C, H } +end + +hsluv.lch_to_luv = function(tuple) + local L = tuple[1]; + local C = tuple[2]; + local Hrad = tuple[3] / 360.0 * 2 * math.pi; + return { L, math.cos(Hrad) * C, math.sin(Hrad) * C }; +end + +hsluv.hsluv_to_lch = function(tuple) + local H = tuple[1]; + local S = tuple[2]; + local L = tuple[3]; + if L > 99.9999999 then + return { 100, 0, H } + end; + if L < 0.00000001 then + return { 0, 0, H } + end; + return { L, hsluv.max_safe_chroma_for_lh(L, H) / 100 * S, H } +end + +hsluv.lch_to_hsluv = function(tuple) + local L = tuple[1]; + local C = tuple[2]; + local H = tuple[3]; + local max_chroma = hsluv.max_safe_chroma_for_lh(L, H) + if L > 99.9999999 then + return { H, 0, 100 } + end; + if L < 0.00000001 then + return { H, 0, 0 } + end; + + return { H, C / max_chroma * 100, L } +end + +hsluv.hpluv_to_lch = function(tuple) + local H = tuple[1]; + local S = tuple[2]; + local L = tuple[3]; + if L > 99.9999999 then + return { 100, 0, H } + end; + if L < 0.00000001 then + return { 0, 0, H } + end; + return { L, hsluv.max_safe_chroma_for_l(L) / 100 * S, H } +end + +hsluv.lch_to_hpluv = function(tuple) + local L = tuple[1]; + local C = tuple[2]; + local H = tuple[3]; + if L > 99.9999999 then + return { H, 0, 100 } + end; + if L < 0.00000001 then + return { H, 0, 0 } + end; + return { H, C / hsluv.max_safe_chroma_for_l(L) * 100, L } +end + +hsluv.rgb_to_hex = function(tuple) + local h = "#"; + for i = 1, 3 do + local c = math.floor(tuple[i] * 255 + 0.5); + local digit2 = math.fmod(c, 16); + local x = (c - digit2) / 16; + local digit1 = math.floor(x); + h = h .. string.sub(hexChars, digit1 + 1, digit1 + 1) + h = h .. string.sub(hexChars, digit2 + 1, digit2 + 1) + end; + return h +end + +hsluv.hex_to_rgb = function(hex) + hex = string.lower(hex) + local ret = {} + for i = 0, 2 do + local char1 = string.sub(hex, i * 2 + 2, i * 2 + 2); + local char2 = string.sub(hex, i * 2 + 3, i * 2 + 3); + local digit1 = string.find(hexChars, char1) - 1 + local digit2 = string.find(hexChars, char2) - 1 + ret[i + 1] = (digit1 * 16 + digit2) / 255.0; + end; + return ret +end + +hsluv.lch_to_rgb = function(tuple) + return hsluv.xyz_to_rgb(hsluv.luv_to_xyz(hsluv.lch_to_luv(tuple))) +end + +hsluv.rgb_to_lch = function(tuple) + return hsluv.luv_to_lch(hsluv.xyz_to_luv(hsluv.rgb_to_xyz(tuple))) +end + +hsluv.hsluv_to_rgb = function(tuple) + return hsluv.lch_to_rgb(hsluv.hsluv_to_lch(tuple)) +end + +hsluv.rgb_to_hsluv = function(tuple) + return hsluv.lch_to_hsluv(hsluv.rgb_to_lch(tuple)) +end + +hsluv.hpluv_to_rgb = function(tuple) + return hsluv.lch_to_rgb(hsluv.hpluv_to_lch(tuple)) +end + +hsluv.rgb_to_hpluv = function(tuple) + return hsluv.lch_to_hpluv(hsluv.rgb_to_lch(tuple)) +end + +hsluv.hsluv_to_hex = function(tuple) + return hsluv.rgb_to_hex(hsluv.hsluv_to_rgb(tuple)) +end + +hsluv.hpluv_to_hex = function(tuple) + return hsluv.rgb_to_hex(hsluv.hpluv_to_rgb(tuple)) +end + +hsluv.hex_to_hsluv = function(s) + return hsluv.rgb_to_hsluv(hsluv.hex_to_rgb(s)) +end + +hsluv.hex_to_hpluv = function(s) + return hsluv.rgb_to_hpluv(hsluv.hex_to_rgb(s)) +end + +hsluv.m = { + { 3.240969941904521, -1.537383177570093, -0.498610760293 }, + { -0.96924363628087, 1.87596750150772, 0.041555057407175 }, + { 0.055630079696993, -0.20397695888897, 1.056971514242878 } +} +hsluv.minv = { + { 0.41239079926595, 0.35758433938387, 0.18048078840183 }, + { 0.21263900587151, 0.71516867876775, 0.072192315360733 }, + { 0.019330818715591, 0.11919477979462, 0.95053215224966 } +} +hsluv.refY = 1.0 +hsluv.refU = 0.19783000664283 +hsluv.refV = 0.46831999493879 +hsluv.kappa = 903.2962962 +hsluv.epsilon = 0.0088564516 + +return hsluv -- cgit v1.2.3