diff --git a/subprojects/bahaghari/lib/colors/hsl.nix b/subprojects/bahaghari/lib/colors/hsl.nix new file mode 100644 index 00000000..8cf1a0a2 --- /dev/null +++ b/subprojects/bahaghari/lib/colors/hsl.nix @@ -0,0 +1,135 @@ +# Similar to RGB, we're also going to base our implementation from looking +# after the W3 CSS Color Module Level 4 specification simply because it is the +# most common one. +{ pkgs, lib, self }: + +let inherit (self.colors) rgb; +in rec { + valueHueMin = 0.0; + valueHueMax = 360.0; + valueParamMin = 0.0; + valueParamMax = 100.0; + + /* Generates an HSL colorspace object to be generated with its method for + convenience. + + Type: HSL :: Attrs -> Attrs + + Example: + HSL { h = 242.0; s = 25; l = 50; } + => { + # The individual hue, saturation, and luminance levels. + + # And several methods. + methods = { + toRgb = ; + lighten = ; + }; + } + */ + HSL = { h, s, l, ... }@color: + assert lib.assertMsg (isHsl color) + "bahaghariLib.colors.hsl.HSL: given color does not have valid HSL value"; + { + inherit h s l; + } // lib.optionalAttrs (color ? a) { inherit (color) a; }; + + /* Returns a boolean if the object is a valid HSL Nix object. + + Type: isHsl :: Attrs -> Bool + + Example: + isHsl { h = 43; s = 89; l = 79; } + => true + + # The hue value is over 360 so it isn't valid. + isHsl { h = 467; s = 78; l = 50; } + => false + + # The lightness is over 100 so not valid either. + isHsl { h = 360; s = 86; l = 120; } + => false + */ + isHsl = { h, s, l, ... }@color: + let + isValidHue = self.math.isWithinRange valueHueMin valueHueMax; + isValidPercentage = self.math.isWithinRange valueParamMin valueParamMax; + in + isValidHue h && isValidPercentage s && isValidPercentage l; + + /* Converts an HSL object to RGB instance. + + Formula is directly taken from the following resource: + https://www.rapidtables.com/convert/color/hsl-to-rgb.html + + Type: toRgb :: Attrs -> Attrs + + Example: + toRgb { h = 234; s = 65; l = 73; } + => { r = 43; g = 52; b = 56 } + */ + toRgb = { h, s, l, ... }@color: + let + inherit (self.colors.rgb) RGB valueMax; + inherit (self.math) abs sub mod'; + + l' = l / valueParamMax; + s' = s / valueParamMax; + + # This may as well turn into Scheme code. + C = (1 - (abs ((2 * l') - 1))) * s'; + X = C * (1 - (abs (sub (mod' (h / 60.0) 2) 1))); + m = l' - (C / 2); + + isHueWithin = min: max: + (h >= min) && (h < max); + rgb' = + if (isHueWithin 0 60) then + { r = C; g = X; b = 0; } + else if (isHueWithin 60 120) then + { r = X; g = C; b = 0; } + else if (isHueWithin 120 180) then + { r = 0; g = C; b = X; } + else if (isHueWithin 180 240) then + { r = 0; g = X; b = C; } + else if (isHueWithin 240 300) then + { r = X; g = 0; b = C; } + else if (isHueWithin 300 360) then + { r = C; g = 0; b = X; } + else throw "WHAT IN THE HELL"; + + scaleValue = x: self.math.round ((x + m) * valueMax); + in + RGB { + r = scaleValue rgb'.r; + g = scaleValue rgb'.g; + b = scaleValue rgb'.b; + }; + + /* Converts an HSL object into an RGB hex string. + + Type: toHex :: Attrs -> String + + Example: + toHex { h = 34; s = 76; l = 100; } + => "FFFFFF" + */ + toHex = color: rgb.toHex (toRgb color); + + /* Converts an HSL object into an RGBA hex string. + + Type: toHex :: Attrs -> String + + Example: + toHex { h = 34; s = 76; l = 100; } + => "FFFFFF" + */ + toHex' = color: rgb.toHex' (toRgb color); + + lighten = { h, s, l, ... }: + percentage: + HSL { + inherit h s; + l = l + percentage; + }; +} diff --git a/subprojects/bahaghari/lib/default.nix b/subprojects/bahaghari/lib/default.nix index 7f370a92..5c3a3c07 100644 --- a/subprojects/bahaghari/lib/default.nix +++ b/subprojects/bahaghari/lib/default.nix @@ -34,6 +34,7 @@ pkgs.lib.makeExtensible # some unbeknownst and probably irrational reason. colors = { rgb = callLibs ./colors/rgb.nix; + hsl = callLibs ./colors/hsl.nix; }; # Dedicated module sets are not supposed to have any of its functions as a diff --git a/subprojects/bahaghari/tests/lib/default.nix b/subprojects/bahaghari/tests/lib/default.nix index 12c0360f..f0cff478 100644 --- a/subprojects/bahaghari/tests/lib/default.nix +++ b/subprojects/bahaghari/tests/lib/default.nix @@ -13,4 +13,5 @@ in trivial = callLib ./trivial; tinted-theming = callLib ./tinted-theming; rgb = callLib ./rgb.nix; + hsl = callLib ./hsl.nix; } diff --git a/subprojects/bahaghari/tests/lib/hsl.nix b/subprojects/bahaghari/tests/lib/hsl.nix new file mode 100644 index 00000000..da25e399 --- /dev/null +++ b/subprojects/bahaghari/tests/lib/hsl.nix @@ -0,0 +1,59 @@ +{ pkgs, lib, self }: + +let + inherit (self.colors.rgb) RGB; + inherit (self.colors.hsl) HSL; + + hslSample = HSL { + h = 254; + s = 100; + l = 45; + }; +in lib.runTests { + testsBasicHsl = { + expr = HSL { + h = 245; + s = 16; + l = 60; + }; + expected = { + h = 245; + s = 16; + l = 60; + }; + }; + + testsBasicHsl2 = { + expr = HSL { + h = 350; + s = 16; + l = 60; + a = 100; + }; + expected = { + h = 350; + s = 16; + l = 60; + a = 100; + }; + }; + + testsToRgb = { + expr = self.colors.hsl.toRgb hslSample; + expected = RGB { + r = 54; + g = 0; + b = 230; + }; + }; + + testsToHex = { + expr = self.colors.hsl.toHex hslSample; + expected = "3600E6"; + }; + + testsToHex' = { + expr = self.colors.hsl.toHex' hslSample; + expected = "3600E6FF"; + }; +}