nix-dotfiles/extra/color/default.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%)
}