lib: add color function collection
This commit is contained in:
parent
3773a25ef1
commit
9d5fcc0668
6 changed files with 512 additions and 1 deletions
65
outputs/lib/color/check.nix
Normal file
65
outputs/lib/color/check.nix
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
{lib, ...}: let
|
||||||
|
inherit (lib) toInt all min max;
|
||||||
|
inherit (builtins) isInt isFloat match getAttr hasAttr;
|
||||||
|
|
||||||
|
# basic checks
|
||||||
|
range = a: b: v: (v <= max a b) && (v >= min a b);
|
||||||
|
number = v: isInt v || isFloat v;
|
||||||
|
unary = range 0.0 1.0;
|
||||||
|
hue = range 0.0 360.0;
|
||||||
|
|
||||||
|
# type checking
|
||||||
|
hex = {
|
||||||
|
string = color: let
|
||||||
|
hexPatternWithHash = match "#[[:xdigit:]]{6}" color;
|
||||||
|
hexPatternNoHash = match "[[:xdigit:]]{6}" color;
|
||||||
|
isValid = hexPatternWithHash != null || hexPatternNoHash != null;
|
||||||
|
in
|
||||||
|
assert isValid || throw "Invalid hex color format: ${color}";
|
||||||
|
hexPatternWithHash != null || hexPatternNoHash != null;
|
||||||
|
set = color: let
|
||||||
|
hasAttributes = all (k: hasAttr k color) ["r" "g" "b"];
|
||||||
|
validPattern = all (k: let
|
||||||
|
v = getAttr k color;
|
||||||
|
in
|
||||||
|
match "[[:xdigit:]]{2}" v != null) ["r" "g" "b"];
|
||||||
|
in
|
||||||
|
hasAttributes && validPattern;
|
||||||
|
};
|
||||||
|
|
||||||
|
rgb = {
|
||||||
|
string = color: let
|
||||||
|
rgbPattern = match "([0-9]{1,3}),([0-9]{1,3}),([0-9]{1,3})" color;
|
||||||
|
toNum = str: let
|
||||||
|
num = toInt str;
|
||||||
|
in
|
||||||
|
num != null && range 0 255 num;
|
||||||
|
isValid = rgbPattern != null && all toNum rgbPattern;
|
||||||
|
in
|
||||||
|
isValid;
|
||||||
|
set = color: let
|
||||||
|
hasAttributes = all (k: hasAttr k color) ["r" "g" "b"];
|
||||||
|
validRanges = all (
|
||||||
|
k: let
|
||||||
|
v = getAttr k color;
|
||||||
|
in
|
||||||
|
number v && range 0 255 v
|
||||||
|
) ["r" "g" "b"];
|
||||||
|
in
|
||||||
|
hasAttributes && validRanges;
|
||||||
|
};
|
||||||
|
|
||||||
|
hsl = {
|
||||||
|
# TODO: add range checks
|
||||||
|
string = color: let
|
||||||
|
hslPattern = match "([0-9]{1,3}),[ ]*([0-9]{1,3})%,[ ]*([0-9]{1,3})%" color;
|
||||||
|
in
|
||||||
|
hslPattern != null;
|
||||||
|
|
||||||
|
set = color: let
|
||||||
|
hasAttributes = all (k: hasAttr k color) ["h" "s" "l"];
|
||||||
|
validRanges = hue color.h && all (k: unary (getAttr k color)) ["s" "l"];
|
||||||
|
in
|
||||||
|
hasAttributes && validRanges;
|
||||||
|
};
|
||||||
|
in {inherit range number unary hue hex rgb hsl;}
|
||||||
207
outputs/lib/color/translate.nix
Normal file
207
outputs/lib/color/translate.nix
Normal file
|
|
@ -0,0 +1,207 @@
|
||||||
|
{
|
||||||
|
math,
|
||||||
|
types,
|
||||||
|
lib,
|
||||||
|
}: let
|
||||||
|
inherit (builtins) substring;
|
||||||
|
inherit (lib) min max;
|
||||||
|
hex = {
|
||||||
|
toSet = string:
|
||||||
|
types.hex.set {
|
||||||
|
r = substring 0 2 string;
|
||||||
|
g = substring 2 2 string;
|
||||||
|
b = substring 4 2 string;
|
||||||
|
};
|
||||||
|
toRGB = let
|
||||||
|
dictionary = {
|
||||||
|
"0" = 0;
|
||||||
|
"1" = 1;
|
||||||
|
"2" = 2;
|
||||||
|
"3" = 3;
|
||||||
|
"4" = 4;
|
||||||
|
"5" = 5;
|
||||||
|
"6" = 6;
|
||||||
|
"7" = 7;
|
||||||
|
"8" = 8;
|
||||||
|
"9" = 9;
|
||||||
|
"a" = 10;
|
||||||
|
"b" = 11;
|
||||||
|
"c" = 12;
|
||||||
|
"d" = 13;
|
||||||
|
"e" = 14;
|
||||||
|
"f" = 15;
|
||||||
|
"A" = 10;
|
||||||
|
"B" = 11;
|
||||||
|
"C" = 12;
|
||||||
|
"D" = 13;
|
||||||
|
"E" = 14;
|
||||||
|
"F" = 15;
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
# Converts a hex pair directly to RGB value (0-255)
|
||||||
|
pair = hexPair: let
|
||||||
|
high = dictionary.${substring 0 1 hexPair};
|
||||||
|
low = dictionary.${substring 1 1 hexPair};
|
||||||
|
in
|
||||||
|
(high * 16) + low;
|
||||||
|
|
||||||
|
# Converts a hex set to RGB set
|
||||||
|
set = hexSet:
|
||||||
|
types.rgb.set {
|
||||||
|
r = hex.toRGB.pair hexSet.r;
|
||||||
|
g = hex.toRGB.pair hexSet.g;
|
||||||
|
b = hex.toRGB.pair hexSet.b;
|
||||||
|
};
|
||||||
|
string = hexStr: let
|
||||||
|
rgbSet = hex.toRGB.set (hex.toSet hexStr);
|
||||||
|
in
|
||||||
|
types.rgb.string rgbSet.r rgbSet.g rgbSet.b;
|
||||||
|
};
|
||||||
|
toHSL = {
|
||||||
|
set = hexStr: let
|
||||||
|
rgbSet = hex.toRGB.set (hex.toSet hexStr);
|
||||||
|
in
|
||||||
|
rgb.toHSL.set rgbSet;
|
||||||
|
|
||||||
|
string = hexStr: let
|
||||||
|
hslSet = hex.toHSL.set hexStr;
|
||||||
|
in
|
||||||
|
types.hsl.string hslSet.h hslSet.s hslSet.l;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
rgb = {
|
||||||
|
toHex = {
|
||||||
|
set = rgbSet: let
|
||||||
|
# Convert decimal to two-digit hex
|
||||||
|
toHexPair = num: let
|
||||||
|
hex = lib.toLower (lib.toHexString num);
|
||||||
|
# Pad with leading zero if single digit
|
||||||
|
padded =
|
||||||
|
if (builtins.stringLength hex) == 1
|
||||||
|
then "0${hex}"
|
||||||
|
else hex;
|
||||||
|
in
|
||||||
|
padded;
|
||||||
|
in
|
||||||
|
types.hex.set {
|
||||||
|
r = toHexPair rgbSet.r;
|
||||||
|
g = toHexPair rgbSet.g;
|
||||||
|
b = toHexPair rgbSet.b;
|
||||||
|
};
|
||||||
|
|
||||||
|
string = rgbStr: let
|
||||||
|
hexSet = rgb.toHex.set rgbStr;
|
||||||
|
in
|
||||||
|
types.hex.string hexSet.r hexSet.g hexSet.b;
|
||||||
|
};
|
||||||
|
toHSL = {
|
||||||
|
set = rgbSet: let
|
||||||
|
# Normalize RGB values to 0-1 range
|
||||||
|
r = rgbSet.r / 255.0;
|
||||||
|
g = rgbSet.g / 255.0;
|
||||||
|
b = rgbSet.b / 255.0;
|
||||||
|
|
||||||
|
# Find min, max and delta
|
||||||
|
c_max = max (max r g) b;
|
||||||
|
c_min = min (min r g) b;
|
||||||
|
delta = c_max - c_min;
|
||||||
|
|
||||||
|
# Calculate HSL values
|
||||||
|
h =
|
||||||
|
if delta == 0.0
|
||||||
|
then 0.0
|
||||||
|
else if c_max == r
|
||||||
|
then 60.0 * (math.mod ((g - b) / delta) 6)
|
||||||
|
else if c_max == g
|
||||||
|
then 60.0 * ((b - r) / delta + 2)
|
||||||
|
else 60.0 * ((r - g) / delta + 4);
|
||||||
|
|
||||||
|
l = (c_max + c_min) / 2;
|
||||||
|
|
||||||
|
s =
|
||||||
|
if delta == 0.0
|
||||||
|
then 0.0
|
||||||
|
else delta / (1 - (math.abs (2 * l - 1)));
|
||||||
|
in
|
||||||
|
types.hsl.set {
|
||||||
|
inherit h;
|
||||||
|
s = math.clamp 0.0 1.0 s;
|
||||||
|
l = math.clamp 0.0 1.0 l;
|
||||||
|
};
|
||||||
|
|
||||||
|
string = rgbStr: let
|
||||||
|
hslSet = rgb.toHSL.set (hex.toRGB.set (hex.toSet rgbStr));
|
||||||
|
in
|
||||||
|
types.hsl.string hslSet.h hslSet.s hslSet.l;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
hsl = {
|
||||||
|
toRGB = {
|
||||||
|
set = hslSet: let
|
||||||
|
inherit (hslSet) h s l;
|
||||||
|
|
||||||
|
# Calculate chroma
|
||||||
|
c = (1 - (math.abs (2 * l - 1))) * s;
|
||||||
|
# Calculate h prime (h')
|
||||||
|
hp = h / 60.0;
|
||||||
|
# Calculate x
|
||||||
|
x = c * (1 - math.abs ((math.mod hp 2) - 1));
|
||||||
|
# Calculate m
|
||||||
|
m = l - c / 2;
|
||||||
|
|
||||||
|
# Get initial RGB values based on h'
|
||||||
|
rgb' =
|
||||||
|
if hp <= 1
|
||||||
|
then {
|
||||||
|
r = c;
|
||||||
|
g = x;
|
||||||
|
b = 0;
|
||||||
|
}
|
||||||
|
else if hp <= 2
|
||||||
|
then {
|
||||||
|
r = x;
|
||||||
|
g = c;
|
||||||
|
b = 0;
|
||||||
|
}
|
||||||
|
else if hp <= 3
|
||||||
|
then {
|
||||||
|
r = 0;
|
||||||
|
g = c;
|
||||||
|
b = x;
|
||||||
|
}
|
||||||
|
else if hp <= 4
|
||||||
|
then {
|
||||||
|
r = 0;
|
||||||
|
g = x;
|
||||||
|
b = c;
|
||||||
|
}
|
||||||
|
else if hp <= 5
|
||||||
|
then {
|
||||||
|
r = x;
|
||||||
|
g = 0;
|
||||||
|
b = c;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
r = c;
|
||||||
|
g = 0;
|
||||||
|
b = x;
|
||||||
|
};
|
||||||
|
# Final RGB values
|
||||||
|
in
|
||||||
|
types.rgb.set {
|
||||||
|
r = math.round ((rgb'.r + m) * 255);
|
||||||
|
g = math.round ((rgb'.g + m) * 255);
|
||||||
|
b = math.round ((rgb'.b + m) * 255);
|
||||||
|
};
|
||||||
|
|
||||||
|
string = hslStr: let
|
||||||
|
rgbSet = hsl.toRGB.set hslStr;
|
||||||
|
in
|
||||||
|
types.rgb.string rgbSet.r rgbSet.g rgbSet.b;
|
||||||
|
};
|
||||||
|
toHex = {
|
||||||
|
set = {};
|
||||||
|
string = color: color;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in {inherit hex hsl rgb;}
|
||||||
51
outputs/lib/color/types.nix
Normal file
51
outputs/lib/color/types.nix
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
{
|
||||||
|
check,
|
||||||
|
math,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
inherit (math) round;
|
||||||
|
hex = {
|
||||||
|
set = {
|
||||||
|
r,
|
||||||
|
g,
|
||||||
|
b,
|
||||||
|
}: let
|
||||||
|
attrs = {inherit r g b;};
|
||||||
|
in
|
||||||
|
assert check.hex.set attrs || throw "Invalid Hex values: r=${toString r}, g=${toString g}, b=${toString b}"; attrs;
|
||||||
|
|
||||||
|
string = r: g: b: let
|
||||||
|
str = "${r}${g}${b}";
|
||||||
|
in
|
||||||
|
assert check.hex.string str || throw "Invalid Hex value: ${str}"; str;
|
||||||
|
};
|
||||||
|
|
||||||
|
rgb = {
|
||||||
|
string = r: g: b: let
|
||||||
|
str = "${toString r},${toString g},${toString b}";
|
||||||
|
in
|
||||||
|
assert check.rgb.string str || throw "Invalid RBG string format: ${str}"; str;
|
||||||
|
set = {
|
||||||
|
r,
|
||||||
|
g,
|
||||||
|
b,
|
||||||
|
}: let
|
||||||
|
attrs = {inherit r g b;};
|
||||||
|
in
|
||||||
|
assert check.rgb.set attrs || throw "Invalid RGB values: r=${toString r}, g=${toString g}, b=${toString b}"; attrs;
|
||||||
|
};
|
||||||
|
hsl = {
|
||||||
|
string = h: s: l: let
|
||||||
|
str = "${toString (round h)}, ${toString (round (s * 100))}%, ${toString (round (l * 100))}%";
|
||||||
|
in
|
||||||
|
assert check.hsl.string str || throw "Invalid HSL values: ${str}"; str;
|
||||||
|
set = {
|
||||||
|
h,
|
||||||
|
s,
|
||||||
|
l,
|
||||||
|
}: let
|
||||||
|
attrs = {inherit h s l;};
|
||||||
|
in
|
||||||
|
assert check.hsl.set attrs || throw "Invalid HSL values: h=${toString h}, s=${toString s}, l=${toString l}"; attrs;
|
||||||
|
};
|
||||||
|
in {inherit hex hsl rgb;}
|
||||||
122
outputs/lib/color/utils.nix
Normal file
122
outputs/lib/color/utils.nix
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
{
|
||||||
|
math,
|
||||||
|
types,
|
||||||
|
translate,
|
||||||
|
}: let
|
||||||
|
# base modification function
|
||||||
|
modifyHSL = hexStr: modifications: let
|
||||||
|
# convert hex to HSL
|
||||||
|
hslSet = translate.hex.toHSL.set hexStr;
|
||||||
|
# apply modifications to get new HSL values
|
||||||
|
newHSL = types.hsl.set {
|
||||||
|
inherit (hslSet) h; # keep hue
|
||||||
|
l = math.clamp 0.0 1.0 (hslSet.l + (modifications.l or 0.0));
|
||||||
|
s = math.clamp 0.0 1.0 (hslSet.s + (modifications.s or 0.0));
|
||||||
|
};
|
||||||
|
# convert back to hex
|
||||||
|
rgbSet = translate.hsl.toRGB.set newHSL;
|
||||||
|
in
|
||||||
|
translate.rgb.toHex.string rgbSet;
|
||||||
|
|
||||||
|
lighten = amount: hexStr:
|
||||||
|
modifyHSL hexStr {l = amount / 100.0;};
|
||||||
|
|
||||||
|
darken = amount: hexStr:
|
||||||
|
modifyHSL hexStr {l = (amount * -1) / 100.0;};
|
||||||
|
|
||||||
|
saturate = amount: hexStr:
|
||||||
|
modifyHSL hexStr {s = amount / 100.0;};
|
||||||
|
|
||||||
|
desaturate = amount: hexStr:
|
||||||
|
modifyHSL hexStr {s = (amount * -1) / 100.0;};
|
||||||
|
|
||||||
|
mkDarkColorScale = base: {
|
||||||
|
up4 = desaturate 24 (lighten 12 base);
|
||||||
|
up3 = desaturate 18 (lighten 9 base);
|
||||||
|
up2 = desaturate 12 (lighten 6 base);
|
||||||
|
up1 = desaturate 6 (lighten 3 base);
|
||||||
|
inherit base;
|
||||||
|
down1 = desaturate 6 (darken 3 base);
|
||||||
|
down2 = desaturate 12 (darken 6 base);
|
||||||
|
down3 = desaturate 18 (darken 9 base);
|
||||||
|
down4 = desaturate 24 (darken 12 base);
|
||||||
|
};
|
||||||
|
|
||||||
|
mkLightColorScale = base: {
|
||||||
|
down4 = desaturate 24 (lighten 12 base);
|
||||||
|
down3 = desaturate 18 (lighten 9 base);
|
||||||
|
down2 = desaturate 12 (lighten 6 base);
|
||||||
|
down1 = desaturate 6 (lighten 3 base);
|
||||||
|
inherit base;
|
||||||
|
up1 = desaturate 6 (darken 3 base);
|
||||||
|
up2 = desaturate 12 (darken 6 base);
|
||||||
|
up3 = desaturate 18 (darken 9 base);
|
||||||
|
up4 = desaturate 24 (darken 12 base);
|
||||||
|
};
|
||||||
|
|
||||||
|
mkDarkColorScheme = {
|
||||||
|
shades,
|
||||||
|
primary,
|
||||||
|
secondary,
|
||||||
|
red,
|
||||||
|
orange,
|
||||||
|
yellow,
|
||||||
|
olive,
|
||||||
|
green,
|
||||||
|
teal,
|
||||||
|
blue,
|
||||||
|
violet,
|
||||||
|
purple,
|
||||||
|
pink,
|
||||||
|
brown,
|
||||||
|
} @ args: {
|
||||||
|
shade-50 = args.shades."50";
|
||||||
|
shade-100 = args.shades."100";
|
||||||
|
shade-150 = args.shades."150";
|
||||||
|
shade-200 = args.shades."200";
|
||||||
|
shade-250 = args.shades."250";
|
||||||
|
shade-300 = args.shades."300";
|
||||||
|
shade-350 = args.shades."350";
|
||||||
|
shade-400 = args.shades."400";
|
||||||
|
shade-450 = args.shades."450";
|
||||||
|
shade-500 = args.shades."500";
|
||||||
|
shade-550 = args.shades."550";
|
||||||
|
shade-600 = args.shades."600";
|
||||||
|
shade-650 = args.shades."650";
|
||||||
|
shade-700 = args.shades."700";
|
||||||
|
shade-750 = args.shades."750";
|
||||||
|
shade-800 = args.shades."800";
|
||||||
|
shade-850 = args.shades."850";
|
||||||
|
shade-900 = args.shades."900";
|
||||||
|
|
||||||
|
primary = mkDarkColorScale args.primary;
|
||||||
|
secondary = {
|
||||||
|
up-1 = args.shade."550";
|
||||||
|
up-2 = args.shade."500";
|
||||||
|
up-3 = args.shade."450";
|
||||||
|
up-4 = args.shade."400";
|
||||||
|
up-5 = args.shade."350";
|
||||||
|
up-6 = args.shade."300";
|
||||||
|
up-7 = args.shade."250";
|
||||||
|
up-8 = args.shade."200";
|
||||||
|
up-9 = args.shade."150";
|
||||||
|
up-10 = args.shade."100";
|
||||||
|
base = args.shade."700";
|
||||||
|
down-1 = args.shade."650";
|
||||||
|
down-2 = args.shade."700";
|
||||||
|
};
|
||||||
|
red = mkDarkColorScale args.red;
|
||||||
|
orange = mkDarkColorScale args.orange;
|
||||||
|
yellow = mkDarkColorScale args.yellow;
|
||||||
|
olive = mkDarkColorScale args.olive;
|
||||||
|
green = mkDarkColorScale args.green;
|
||||||
|
teal = mkDarkColorScale args.teal;
|
||||||
|
blue = mkDarkColorScale args.blue;
|
||||||
|
violet = mkDarkColorScale args.violet;
|
||||||
|
purple = mkDarkColorScale args.purple;
|
||||||
|
pink = mkDarkColorScale args.pink;
|
||||||
|
brown = mkDarkColorScale args.brown;
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
inherit lighten darken saturate desaturate mkLightColorScale mkDarkColorScale mkDarkColorScheme;
|
||||||
|
}
|
||||||
|
|
@ -4,10 +4,30 @@
|
||||||
inputs,
|
inputs,
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
# My person functions
|
# my scuffed lib
|
||||||
ook-lib = {
|
ook-lib = {
|
||||||
builders = import ./builders.nix {inherit self lib inputs;};
|
builders = import ./builders.nix {inherit self lib inputs;};
|
||||||
mkNeovim = import ./mkNeovim.nix {inherit inputs;};
|
mkNeovim = import ./mkNeovim.nix {inherit inputs;};
|
||||||
|
math = import ./math.nix {inherit lib;};
|
||||||
|
color = let
|
||||||
|
check = import ./color/check.nix {inherit lib;};
|
||||||
|
types = import ./color/types.nix {
|
||||||
|
inherit (ook-lib) math;
|
||||||
|
inherit check;
|
||||||
|
};
|
||||||
|
translate = import ./color/translate.nix {
|
||||||
|
inherit lib;
|
||||||
|
inherit (ook-lib) math;
|
||||||
|
inherit types;
|
||||||
|
};
|
||||||
|
utils = import ./color/utils.nix {
|
||||||
|
inherit (ook-lib) math;
|
||||||
|
inherit types translate;
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
inherit check types translate;
|
||||||
|
inherit (utils) lighten darken saturate desaturate mkColorScale;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
in {
|
in {
|
||||||
_module.args.ook.lib = ook-lib;
|
_module.args.ook.lib = ook-lib;
|
||||||
|
|
|
||||||
46
outputs/lib/math.nix
Normal file
46
outputs/lib/math.nix
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
{lib}: let
|
||||||
|
inherit (lib) min max;
|
||||||
|
inherit (builtins) floor ceil;
|
||||||
|
|
||||||
|
# basic math functions
|
||||||
|
# credits to github:xddxdd/nix-math
|
||||||
|
|
||||||
|
abs = x:
|
||||||
|
if x < 0
|
||||||
|
then 0 - x
|
||||||
|
else x;
|
||||||
|
|
||||||
|
clamp = a: b: v: min (max v (min a b)) (max a b);
|
||||||
|
|
||||||
|
round = x:
|
||||||
|
if (x - floor x) < 0.5
|
||||||
|
then floor x
|
||||||
|
else ceil x;
|
||||||
|
|
||||||
|
hasFraction = x: let
|
||||||
|
splitted = lib.splitString "." (builtins.toString x);
|
||||||
|
in
|
||||||
|
builtins.length splitted >= 2 && builtins.length (builtins.filter (ch: ch != "0") (lib.stringToCharacters (builtins.elemAt splitted 1))) > 0;
|
||||||
|
|
||||||
|
div = a: b: let
|
||||||
|
divideExactly = !(hasFraction (1.0 * a / b));
|
||||||
|
offset =
|
||||||
|
if divideExactly
|
||||||
|
then 0
|
||||||
|
else (0 - 1);
|
||||||
|
in
|
||||||
|
if b < 0
|
||||||
|
then offset - div a (0 - b)
|
||||||
|
else if a < 0
|
||||||
|
then offset - div (0 - a) b
|
||||||
|
else floor (1.0 * a / b);
|
||||||
|
|
||||||
|
mod = a: b:
|
||||||
|
if b < 0
|
||||||
|
then 0 - mod (0 - a) (0 - b)
|
||||||
|
else if a < 0
|
||||||
|
then mod (b - mod (0 - a) b) b
|
||||||
|
else a - b * (div a b);
|
||||||
|
in {
|
||||||
|
inherit round mod abs hasFraction clamp;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue