lib: add color function collection

This commit is contained in:
ooks-io 2024-11-06 18:49:36 +11:00
parent 3773a25ef1
commit 9d5fcc0668
6 changed files with 512 additions and 1 deletions

View 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;}

View 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;}

View 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
View 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;
}

View file

@ -4,10 +4,30 @@
inputs,
...
}: let
# My person functions
# my scuffed lib
ook-lib = {
builders = import ./builders.nix {inherit self lib 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 {
_module.args.ook.lib = ook-lib;

46
outputs/lib/math.nix Normal file
View 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;
}