Suggest clamped chroma for out-of-gamut OKLCH

This commit is contained in:
Andrew Kvalheim 2024-04-25 08:03:18 -07:00
parent 3a0e6a9b7f
commit a881ba4f77
2 changed files with 41 additions and 21 deletions

View file

@ -1,8 +1,8 @@
{ lib }:
let
inherit (builtins) add floor mapAttrs split stringLength;
inherit (lib) concatLines fixedWidthNumber fold isList max removeSuffix splitString throwIfNot toHexString;
inherit (builtins) add all attrValues floor mapAttrs split stringLength;
inherit (lib) concatLines fixedWidthNumber fold isList max removeSuffix splitString throwIf throwIfNot toHexString;
inherit (lib.strings) replicate;
inherit (import <nix-math> { inherit lib; }) cos pi polynomial pow sin;
@ -39,6 +39,14 @@ let
];
in
rec {
findHighest = accept: resolution: low: high:
if high - low < resolution then
throwIfNot (accept low) "Lower bound ${toString low} fails acceptance test" low
else
let half = low + (high - low) * 0.5; in
if accept half then findHighest accept resolution half high
else findHighest accept resolution low half;
frame = color: text:
let
lines = splitString "\n" (removeSuffix "\n" text);
@ -52,26 +60,39 @@ rec {
(color "${pad "" ""}")
]);
oklchToRgb = { l, c, h }:
let
# Adapted from https://drafts.csswg.org/css-color-4/#color-conversion-code
a = c * cos (h * pi / 180);
b = c * sin (h * pi / 180);
oklchToCss = { l, c, h }: "oklch(${toString l} ${toString c} ${toString h})";
# Adapted from https://bottosson.github.io/posts/oklab/#converting-from-linear-srgb-to-oklab
long = pow (l + 0.3963377774 * a + 0.2158037573 * b) 3;
medium = pow (l - 0.1055613458 * a - 0.0638541728 * b) 3;
short = pow (l - 0.0894841775 * a - 1.2914855480 * b) 3;
linear = {
r = 4.0767416621 * long - 3.3077115913 * medium + 0.2309699292 * short;
g = -1.2684380046 * long + 2.6097574011 * medium - 0.3413193965 * short;
b = -0.0041960863 * long - 0.7034186147 * medium + 1.7076147010 * short;
};
oetf = l':
throwIfNot (0 <= l' && l' <= 1) "oklch(${toString l} ${toString c} ${toString h}) is not representable in sRGB"
(if l' > 0.0031308 then 1.055 * (pow124 l') - 0.055 else 12.92 * l');
oklchToRgb = target:
let
toLinearRgb = { l, c, h }:
let
# Adapted from https://drafts.csswg.org/css-color-4/#color-conversion-code
a = c * cos (h * pi / 180);
b = c * sin (h * pi / 180);
# Adapted from https://bottosson.github.io/posts/oklab/#converting-from-linear-srgb-to-oklab
long = pow (l + 0.3963377774 * a + 0.2158037573 * b) 3;
medium = pow (l - 0.1055613458 * a - 0.0638541728 * b) 3;
short = pow (l - 0.0894841775 * a - 1.2914855480 * b) 3;
rgb = {
r = 4.0767416621 * long - 3.3077115913 * medium + 0.2309699292 * short;
g = -1.2684380046 * long + 2.6097574011 * medium - 0.3413193965 * short;
b = -0.0041960863 * long - 0.7034186147 * medium + 1.7076147010 * short;
};
in
if all (u: 0 <= u && u <= 1) (attrValues rgb) then rgb else null;
# Adapted from https://bottosson.github.io/posts/colorwrong/#what-can-we-do%3F
oetf = u: if u > 0.0031308 then 1.055 * (pow124 u) - 0.055 else 12.92 * u;
inGamut = c: toLinearRgb (target // { inherit c; }) != null;
clamped = target // { c = findHighest inGamut 0.0000005 0 0.37; };
linearRgb = toLinearRgb target;
in
mapAttrs (_: oetf) linear;
throwIf (linearRgb == null)
"Not representable in sRGB\n Target: ${oklchToCss target}\n Clamped: ${oklchToCss clamped}"
(mapAttrs (_: oetf) linearRgb);
printableLength = text: fold add 0 (map (v: if isList v then 0 else stringLength v) (split "\\[[^m]*m" text));

View file

@ -5,7 +5,6 @@ let
inherit (lib) concatLines imap0 mapAttrsRecursive mapAttrsToList nameValuePair;
inherit (import ./lib.nix { inherit lib; }) oklchToRgb rgbToHex round;
# TODO: Automatically clamp to sRGB
dark = c: c // { l = 0.35; c = if c.c == 0 then 0 else 0.060; };
dim = c: c // { l = 0.50; c = if c.c == 0 then 0 else 0.150; };
contrast-minimum = c: c // { l = c.l + 0.25 * (if c.l < 0.5 then 1 else - 1); };