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.

Revisions

  1. Eroica created this gist Jul 30, 2017.
    236 changes: 236 additions & 0 deletions happylittlecolors.lua
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,236 @@
    --
    -- 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}