303 lines
9.9 KiB
Nix
303 lines
9.9 KiB
Nix
{ pkgs, lib ? pkgs.lib, hex, float, ... }:
|
|
let
|
|
inherit (builtins) isInt isString isFloat trace div isAttrs hasAttr;
|
|
inherit (lib.trivial) min max;
|
|
inherit (lib.lists) head tail drop last;
|
|
inherit (lib.strings) concatMapStrings fixedWidthString match toLower toInt;
|
|
|
|
## OPERATORS
|
|
# Module operator implementation for floats
|
|
div' = n: d: float.floor (div (float.ensureFloat n) (float.ensureFloat d));
|
|
mod' = n: d:
|
|
let
|
|
f = div' n d;
|
|
in
|
|
n - (float.fromInt f) * d;
|
|
|
|
# Absolute operator implementation
|
|
_abs = v: if v < 0 then (-v) else v;
|
|
|
|
# Check if `v` is between `a` and `b`
|
|
_inRange = a: b: v: (v <= max a b) && (v >= min a b);
|
|
|
|
# Clamp `v` between `a` and `v`
|
|
_clamp = a: b: v: min (max v (min a b)) (max a b);
|
|
|
|
## 8BIT
|
|
# Check if `v` is in 8Bit format
|
|
_is8Bit = v: _inRange 0.0 255.0 v;
|
|
|
|
# Clamp 8bit value
|
|
_clamp8Bit = _clamp 0.0 255.0;
|
|
|
|
# Apply function to 8bit value and clamp the result
|
|
_tclamp8Bit = f: v: _clamp8Bit (f v);
|
|
|
|
## UNARY
|
|
# Check if input is in [0, 1]
|
|
_isUnary = _inRange 0.0 1.0;
|
|
|
|
# Clamp input to [0, 1]
|
|
_clampUnary = _clamp 0.0 1.0;
|
|
|
|
# Apply function to unary value and clamp the result
|
|
_tclampUnary = f: v: _clampUnary (f v);
|
|
|
|
|
|
# Check if input is in [0, 360]
|
|
_isHue = _inRange 0.0 360.0;
|
|
|
|
# Apply function to hue value and map the result in [0, 360)
|
|
_tHue = f: v: mod' (f v) 360.0;
|
|
|
|
# RGB constructor
|
|
_rgba = { r, g, b, a ? 255.0 }:
|
|
assert (_is8Bit r);
|
|
assert (_is8Bit g);
|
|
assert (_is8Bit b);
|
|
{ inherit r g b a; };
|
|
|
|
# HSLA constructor
|
|
_hsla = { h, l, s ? 1.0, a ? 255.0 }:
|
|
assert (_inRange 0.0 360.0 h);
|
|
assert (_isUnary s);
|
|
assert (_isUnary l);
|
|
assert (_isUnary a);
|
|
{ inherit h s l a; };
|
|
|
|
in
|
|
rec {
|
|
|
|
inherit div' mod' _abs _inRange _clamp _is8Bit _clamp8Bit _tclamp8Bit _isUnary _clampUnary _tclampUnary _tHue _rgba _hsla;
|
|
|
|
rgba = _rgba;
|
|
|
|
isRgba = c: if isAttrs c && hasAttr "r" c && hasAttr "g" c && hasAttr "b" c && hasAttr "a" c then
|
|
0 <= c.r && c.r <= 255 && 0 <= c.g && c.g <= 255 && 0 <= c.b && c.b <= 255 && 0 <= c.a && c.a <= 255
|
|
else false;
|
|
|
|
hsla = _hsla;
|
|
|
|
isHsla = c: if isAttrs c hasAttr "h" c && hasAttr "s" c && hasAttr "l" c && hasAttr "a" c then
|
|
0 <= c.h && c.h <= 255 && 0 <= c.s && c.s <= 255 && 0 <= c.l && c.l <= 255 && 0 <= c.a && c.a <= 255
|
|
else false;
|
|
|
|
## CONVERSION
|
|
# RGB to HSL
|
|
rgbaToHsla = color:
|
|
let
|
|
c_color = _rgba color;
|
|
r = c_color.r / 255.0;
|
|
g = c_color.g / 255.0;
|
|
b = c_color.b / 255.0;
|
|
a = c_color.a / 255.0;
|
|
c_min = min (min r g) b;
|
|
c_max = max (max r g) b;
|
|
delta = c_max - c_min;
|
|
|
|
hue = (
|
|
if delta == 0.0 then 0.0 else
|
|
if r == c_max then (mod' (((g - b) / delta) + 6) 6) else
|
|
if g == c_max then (b - r) / delta + 2 else
|
|
assert b == c_max; (r - g) / delta + 4
|
|
) * 60;
|
|
lightness = (c_min + c_max) / 2.0;
|
|
saturation =
|
|
if delta == 0.0 then 0.0 else
|
|
delta / (1 - _abs (2.0 * lightness - 1.0));
|
|
in
|
|
assert (isRgba color);
|
|
_hsla {
|
|
l = _clampUnary lightness;
|
|
s = _clampUnary saturation;
|
|
h = mod' hue 360.0;
|
|
a = _clampUnary a;
|
|
};
|
|
|
|
# HSL to RGB
|
|
hslToRgb = color:
|
|
let
|
|
# check if `v` is in [a, b)
|
|
_checkRange = a: b: v: a <= v && v < b;
|
|
c_color = _hsla color;
|
|
h = c_color.h;
|
|
s = c_color.s;
|
|
l = c_color.l;
|
|
a = c_color.a;
|
|
c = (1 - (_abs (2 * l - 1))) * s;
|
|
x = c * (1 - _abs ((mod' (h / 60) 2) - 1));
|
|
m = l - (c / 2.0);
|
|
|
|
r' = if _inRange 120 240 h then 0 else
|
|
if _inRange 60 300 h then x else
|
|
c;
|
|
g' = if _inRange 60 180 h then c else
|
|
if _inRange 0 240 h then x else
|
|
0;
|
|
b' = if _inRange 180 300 h then c else
|
|
if _inRange 120 360 h then x else
|
|
0;
|
|
in
|
|
assert (isHsla);
|
|
_rgba {
|
|
r = _clamp8Bit ((r' + m) * 255.0);
|
|
g = _clamp8Bit ((g' + m) * 255.0);
|
|
b = _clamp8Bit ((b' + m) * 255.0);
|
|
a = _clamp8Bit (a * 255.0);
|
|
};
|
|
|
|
## TRANSFORM
|
|
|
|
tRedRgba = f: color: assert (isRgba color); color // { r = _tclamp8Bit f color.r; };
|
|
tGreenRgba = f: color: assert (isRgba color); color // { g = _tclamp8Bit f color.g; };
|
|
tBlueRgba = f: color: assert (isRgba color); color // { b = _tclamp8Bit f color.b; };
|
|
tAlphaRgba = f: color: assert (isRgba color); color // { a = _tclamp8Bit f color.a; };
|
|
|
|
setRedRgba = r: color: tGreenRgba (v: r) color;
|
|
setGreenRgba = g: color: tBlueRgba (v: g) color;
|
|
setBlueRgba = b: color: tAlphaRgba (v: b) color;
|
|
setAlphaRgba = a: color: tRedRgba (v: a) color;
|
|
|
|
tRedHsla = f: color: assert (isHsla color); color // { h = _tHue f color.h; };
|
|
tGreenHsla = f: color: assert (isHsla color); color // { s = _tclampUnary f color.s; };
|
|
tBlueHsla = f: color: assert (isHsla color); color // { l = _tclampUnary f color.l; };
|
|
tAlphaHsla = f: color: assert (isHsla color); color // { a = _tclampUnary f color.a; };
|
|
|
|
setRedHsla = h: color: tGreenHsla (v: h) color;
|
|
setGreenHsla = s: color: tBlueHsla (v: s) color;
|
|
setBlueHsla = l: color: tAlphaHsla (v: l) color;
|
|
setAlphaHsla = a: color: tRedHsla (v: a) color;
|
|
|
|
## RGB TRANSFORM
|
|
# Add brightness value as integer value or percent value
|
|
brighten = color: value:
|
|
let
|
|
directValue = if isInt value || isFloat value then value else null;
|
|
positiveMatches = match "([[:digit:]]+)%" value;
|
|
negativeMatches = match "-([[:digit:]]+)%" value;
|
|
percentValue =
|
|
if positiveMatches != null then toInt (head positiveMatches)
|
|
else if negativeMatches != null then (-toInt (head negativeMatches))
|
|
else null;
|
|
valueTransform = v:
|
|
if directValue != null then _clamp8Bit (v + directValue)
|
|
else _clamp8Bit (v * (100.0 + percentValue) / 100.0);
|
|
in
|
|
tBlueRgba valueTransform (tGreenRgba valueTransform (tRedRgba valueTransform color));
|
|
|
|
darken = color: value:
|
|
let
|
|
directValue = if isInt value || isFloat value then value else null;
|
|
positiveMatches = match "([[:digit:]]+)%" value;
|
|
negativeMatches = match "-([[:digit:]]+)%" value;
|
|
percentValue =
|
|
if positiveMatches != null then toInt (head positiveMatches)
|
|
else if negativeMatches != null then (-toInt (head negativeMatches))
|
|
else null;
|
|
valueTransform = v:
|
|
if directValue != null then _clamp8Bit (v - directValue)
|
|
else _clamp8Bit (v * (100.0 - percentValue) / 100.0);
|
|
in
|
|
tBlueRgba valueTransform (tGreenRgba valueTransform (tRedRgba valueTransform color));
|
|
|
|
## DESERIALIZATION
|
|
# Parse a hex color string to a RGBA color
|
|
hexToRgba = s:
|
|
let
|
|
rgba = match "#([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{2})" s;
|
|
rgb = match "#([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{2})" s;
|
|
hex_list = (if isNull rgb then rgba else rgb ++ [ "FF" ]);
|
|
values = map (s: float.fromInt (hex.toDec s)) hex_list;
|
|
in
|
|
_rgba {
|
|
r = head values;
|
|
g = head (tail values);
|
|
b = head (drop 2 values);
|
|
a = last values;
|
|
};
|
|
|
|
# Parse a hex color string to a HSLA color
|
|
hexToHsla = s:
|
|
let
|
|
hsla = match "#([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{2})" s;
|
|
hsl = match "#([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{2})" s;
|
|
hex_list = (if isNull hsl then hsla else hsl ++ [ "FF" ]);
|
|
values = map (s: float.fromInt (hex.toDec s)) hex_list;
|
|
in
|
|
_hsla {
|
|
h = head values;
|
|
s = head (tail values);
|
|
l = head (drop 2 values);
|
|
a = last values;
|
|
};
|
|
|
|
|
|
## SERIALIZATION
|
|
# Print RGB color as uppercase hex string
|
|
toRGBHex = color:
|
|
let
|
|
inherit (color) r g b;
|
|
in
|
|
assert (isRgba color);
|
|
''#${concatMapStrings (v: fixedWidthString 2 "0" (hex.fromDec (float.round v))) [ r g b ]}'';
|
|
|
|
# Print RGBA color as uppercase hex string
|
|
toRGBAHex = color:
|
|
let
|
|
inherit (color) r g b a;
|
|
in
|
|
assert (isRgba color);
|
|
''#${concatMapStrings (v: fixedWidthString 2 "0" (hex.fromDec (float.round v))) [ r g b a ]}'';
|
|
|
|
# Print RGBA color as uppercase hex string in the form ARGB (Polybar uses this format)
|
|
toARGBHex = color:
|
|
let
|
|
inherit (color) r g b a;
|
|
in
|
|
assert (isRgba color);
|
|
''#${concatMapStrings (v: fixedWidthString 2 "0" (hex.fromDec (float.round v))) [ a r g b ]}'';
|
|
|
|
|
|
# Print RGB color as lowercase hex string
|
|
toRgbHex = color: toLower (toRGBHex color);
|
|
|
|
# Print RGBA color as lowercase hex string
|
|
toRgbaHex = color: toLower (toRGBAHex color);
|
|
|
|
# Print RGBA color as lowercase hex string in the form argb (Polybar uses this format)
|
|
toArgbHex = color: toLower (toArgbHex color);
|
|
|
|
# Print RGB color as uppercase hex string
|
|
toHSLHex = color:
|
|
let
|
|
inherit (color) h s l;
|
|
in
|
|
assert (isHsla color);
|
|
''#${concatMapStrings (v: fixedWidthString 2 "0" (hex.fromDec (float.round v))) [ h s l ]}'';
|
|
|
|
# Print HSLA color as uppercase hex string
|
|
toHSLAHex = color:
|
|
let
|
|
inherit (color) h s l a;
|
|
in
|
|
assert (isHsla color);
|
|
''#${concatMapStrings (v: fixedWidthString 2 "0" (hex.fromDec (float.round v))) [ h s l a ]}'';
|
|
|
|
# Print HSL color as lowercase hex string
|
|
toHslHex = color: toLower (toHSLHex color);
|
|
|
|
# Print HSLA color as lowercase hex string
|
|
toHslaHex = color: toLower (toHSLAHex color);
|
|
|
|
|
|
## CONSTANTS
|
|
black = hexToRgba "#000000"; # RGB (0,0,0) HSL (0°,0%,0%)
|
|
white = hexToRgba "#FFFFFF"; # RGB (255,255,255) HSL (0°,0%,100%)
|
|
red = hexToRgba "#FF0000"; # RGB (255,0,0) HSL (0°,100%,50%)
|
|
green = hexToRgba "#00FF00"; # RGB (0,255,0) HSL (120°,100%,50%)
|
|
blue = hexToRgba "#0000FF"; # RGB (0,0,255) HSL (240°,100%,50%)
|
|
yellow = hexToRgba "#FFFF00"; # RGB (255,255,0) HSL (60°,100%,50%)
|
|
cyan = hexToRgba "#00FFFF"; # RGB (0,255,255) HSL (180°,100%,50%)
|
|
magenta = hexToRgba "#FF00FF"; # RGB (255,0,255) HSL (300°,100%,50%)
|
|
transparent = hexToRgba "#00000000"; # RGBA (0,0,0,0) HSLA (0°,0%,0%,0%)
|
|
}
|