bahaghari/lib: implement basic HSL color namespace

This commit is contained in:
Gabriel Arazas 2024-06-15 13:48:34 +08:00
parent a170fd8344
commit 2576ef4e43
No known key found for this signature in database
GPG Key ID: 62104B43D00AA360
4 changed files with 196 additions and 0 deletions

View File

@ -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 = <function>;
lighten = <function>;
};
}
*/
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;
};
}

View File

@ -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

View File

@ -13,4 +13,5 @@ in
trivial = callLib ./trivial;
tinted-theming = callLib ./tinted-theming;
rgb = callLib ./rgb.nix;
hsl = callLib ./hsl.nix;
}

View File

@ -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";
};
}