diff options
-rw-r--r-- | class.lua | 38 | ||||
-rw-r--r-- | hsluv.lua | 347 | ||||
-rw-r--r-- | main_old.lua | 86 |
3 files changed, 471 insertions, 0 deletions
diff --git a/class.lua b/class.lua new file mode 100644 index 0000000..c9fb87b --- /dev/null +++ b/class.lua @@ -0,0 +1,38 @@ +-- minimal class library +-- +-- local Foo = class() +-- function Foo.make(cls, x,y,z) +-- return setmetatable({x=x,y=y,z=z},cls) +-- end +-- function Foo.whatever(self, ...) +-- ... +-- end +-- ... +-- local foo = Foo:make(...) +-- foo:whatever(...) + + +local function class() + local T = {} + T.__index = T + return T +end + +local function extend(Base) + local T = {} + T.__index = T + for k,v in pairs(Base) do + if k:sub(1,2) == "__" and k~="__index" then + T[k]=v + end + end + setmetatable(T,{__index=Base}) + return T +end + +return setmetatable({ + class=class, + extend=extend +},{__call=class}) + + 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 diff --git a/main_old.lua b/main_old.lua new file mode 100644 index 0000000..160a0d7 --- /dev/null +++ b/main_old.lua @@ -0,0 +1,86 @@ +local class = require 'class' +local hsluv = require 'hsluv' + +local tau = 2*math.pi +local ZOOM = 200 +local G = love.graphics + +local function write_at(text,x,y) + G.push() + G.translate(x,y) + G.scale(1/ZOOM) + G.print(text,0,0) + G.pop() +end + +local Port = class() +Port.R = 0.1 +function Port.make(cls, x,y, num) + return setmetatable({x=x,y=y,n=num,wires={}},cls) +end +function Port.draw(self, istate) + local c = {0,0,0} + if istate == 'hover' then + c[1] = 1 + end + love.graphics.setColor(c) + G.circle('line',self.x,self.y,self.R) + write_at(self.n, self.x, self.y) +end +function Port.contains(self, px,py) + local d = math.sqrt((self.x - px)^2 + (self.y - py)^2) + return d <= self.R +end + +local ports = {} +local n = 10 +for i = 1,n do + local theta = tau * i/n + table.insert(ports, Port:make(math.cos(theta), math.sin(theta), i)) +end +ports[3].wires = {ports[6]} +ports[5].wires = {ports[7],ports[2]} +for i = 1,15 do + local r1,r2 = math.random(10), math.random(10) + if r1 ~= r2 then + table.insert(ports[r1].wires, ports[r2]) + end +end + + +local function catenary(x1,y1,x2,y2) + -- https://math.stackexchange.com/questions/3557767/how-to-construct-a-catenary-of-a-specified-length-through-two-specified-points + +end + +local function wire_color(n) + local phi = (1+math.sqrt(5))/2 + local h = (360*phi*n)%360 + return hsluv.hsluv_to_rgb({h, 80, 60}) +end + +local state = 'normal' + + +function love.draw() + local W,H = G.getDimensions() + G.clear(1,1,1) + G.setColor(0,0,0) + G.origin() + G.setLineWidth(0.01) + G.translate(W/2,H/2) + G.scale(ZOOM) + + local mx,my = G.inverseTransformPoint(love.mouse.getPosition()) + local wn = 0 + for i,p in ipairs(ports) do + local istate = p:contains(mx,my) and 'hover' or 'normal' + p:draw(istate) + for _,q in ipairs(p.wires) do + G.setColor(wire_color(wn)) + wn = wn + 1 + catenary(p.x,p.y,q.x,q.y) + end + end +end + |