Skip to content

Instantly share code, notes, and snippets.

@Eroica
Created July 30, 2017 16:32
Show Gist options
  • Save Eroica/8fb013bdd676d69eaa80f0b535b89c0c to your computer and use it in GitHub Desktop.
Save Eroica/8fb013bdd676d69eaa80f0b535b89c0c to your computer and use it in GitHub Desktop.
Lua library for color space conversions
--
-- HappyLittleColors.lua
--
-- Copyright (C) 2017 Eroica
--
-- This software is provided 'as-is', without any express or implied
-- warranty. In no event will the authors be held liable for any damages
-- arising from the use of this software.
--
-- Permission is granted to anyone to use this software for any purpose,
-- including commercial applications, and to alter it and redistribute it
-- freely, subject to the following restrictions:
--
-- 1. The origin of this software must not be misrepresented; you must not
-- claim that you wrote the original software. If you use this software
-- in a product, an acknowledgment in the product documentation would be
-- appreciated but is not required.
-- 2. Altered source versions must be plainly marked as such, and must not be
-- misrepresented as being the original software.
-- 3. This notice may not be removed or altered from any source distribution.
--
local class_constructor = {
__call = function (cls, ...)
local _ = cls.New(...)
-- Disable creation of new keys
cls.__newindex = function (t, k, v) return end
return _
end,
}
-- Forward declarations for local functions. Should improve that some day ...
local PivotRgb
local PivotXyz
local ToRgb
local XyzColor
local LabColor
local RgbColor = {}
RgbColor.__index = RgbColor
setmetatable(RgbColor, class_constructor)
function RgbColor.New (r, g, b)
return setmetatable({R = r, G = g, B = b}, RgbColor)
end
function RgbColor.__eq (lhs, rhs)
return lhs.R == rhs.R and lhs.G == rhs.G and lhs.B and rhs.B
end
function RgbColor.__tostring (self)
return "(" .. self.R .. ", " .. self.G .. ", " .. self.B .. ")"
end
function RgbColor:Copy ()
return RgbColor(self.R, self.G, self.B)
end
function RgbColor:ToXyz ()
local r = PivotRgb(self.R / 255.0)
local g = PivotRgb(self.G / 255.0)
local b = PivotRgb(self.B / 255.0)
return XyzColor(r * 0.4124 + g * 0.3576 + b * 0.1805,
r * 0.2126 + g * 0.7152 + b * 0.0722,
r * 0.0193 + g * 0.1192 + b * 0.9505)
end
function RgbColor:ToLab ()
return self:ToXyz():ToLab()
end
XyzColor = {}
XyzColor.__index = XyzColor
setmetatable(XyzColor, class_constructor)
function XyzColor.New (X, Y, Z)
-- Create closures for some constant values, otherwise one could just do
-- getmetatable(xyz_color).Epsilon = -1
local WhiteReference = {X = 95.047, Y = 100.000, Z = 108.883}
local Epsilon = 0.008856
local Kappa = 903.3
function XyzColor.WhiteReference() return WhiteReference end
function XyzColor.Epsilon() return Epsilon end
function XyzColor.Kappa() return Kappa end
return setmetatable({X = X, Y = Y, Z = Z}, XyzColor)
end
function XyzColor.__eq (lhs, rhs)
return lhs.X == rhs.X and lhs.Y == rhs.Y and lhs.Z and rhs.Z
end
function XyzColor.__tostring (self)
return "(" .. self.X .. ", " .. self.Y .. ", " .. self.Z .. ")"
end
function XyzColor:Copy ()
return XyzColor(self.X, self.Y, self.Z)
end
function XyzColor:ToLab ()
local white = self.WhiteReference()
local x = PivotXyz(self.X / white.X)
local y = PivotXyz(self.Y / white.Y)
local z = PivotXyz(self.Z / white.Z)
return LabColor(math.max(0, 116 * y - 16),
500 * (x - y),
200 * (y - z))
end
function XyzColor:ToRgb ()
local x = self.X / 100.0
local y = self.Y / 100.0
local z = self.Z / 100.0
local r = x * 3.2406 + y * -1.5372 + z * -0.4986
local g = x * -0.9689 + y * 1.8758 + z * 0.0415
local b = x * 0.0557 + y * -0.2040 + z * 1.0570
if r > 0.0031308 then
r = 1.055 * math.pow(r, 1 / 2.4) - 0.055
else
r = 12.92 * r
end
if g > 0.0031308 then
g = 1.055 * math.pow(g, 1 / 2.4) - 0.055
else
g = 12.92 * g
end
if b > 0.0031308 then
b = 1.055 * math.pow(b, 1 / 2.4) - 0.055
else
b = 12.92 * b
end
return RgbColor(ToRgb(r),
ToRgb(g),
ToRgb(b))
end
LabColor = {}
LabColor.__index = LabColor
setmetatable(LabColor, class_constructor)
function LabColor.New (L, a, b)
return setmetatable({L = L, a = a, b = b}, LabColor)
end
function LabColor.__eq (lhs, rhs)
return lhs.L == rhs.L and lhs.a == rhs.a and lhs.b and rhs.b
end
function LabColor.__tostring (self)
return "(" .. self.L .. "," .. self.a .. "," .. self.b .. ")"
end
function LabColor:ToRgb ()
local y = (self.L + 16.0) / 116.0
local x = self.a / 500.0 + y
local z = y - self.b / 200.0
local white = XyzColor.WhiteReference()
local epsilon = XyzColor.Epsilon()
local kappa = XyzColor.Kappa()
local x3 = x * x * x;
local z3 = z * z * z;
local xyz = {}
if x3 > epsilon then
xyz.X = white.X * x3
else
xyz.X = white.X * (x - 16.0 / 116.0) / 7.787
end
if self.L > (kappa * epsilon) then
xyz.Y = white.Y * math.pow(((self.L + 16.0) / 116.0), 3)
else
xyz.Y = white.Y * self.L / kappa
end
if z3 > epsilon then
xyz.Z = white.Z * z3
else
xyz.Z = white.Z * (z - 16.0 / 116.0) / 7.787
end
return XyzColor(xyz.X, xyz.Y, xyz.Z):ToRgb()
end
PivotRgb = function (n)
if n > 0.04045 then
return math.pow((n + 0.055) / 1.055, 2.4) * 100.0
else
return (n/12.92) * 100.0
end
end
ToRgb = function (n)
local result = 255.0 * n
if result < 0 then return 0 end
if result > 255 then return 255 end
return math.floor(result + 0.5)
end
PivotXyz = function (n)
if n > XyzColor.Epsilon() then
return n ^ (1/3)
else
return (XyzColor.Kappa() * n + 16) / 116
end
end
return {Rgb = RgbColor,
Xyz = XyzColor,
Lab = LabColor}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment