From 32a8324a6ffba2a28e6a30c686c9249dec8b0ed5 Mon Sep 17 00:00:00 2001 From: facelessuser Date: Thu, 16 May 2024 15:08:52 -0600 Subject: [PATCH 1/3] Ensure Okhsl and Okhsv return undefined hues for achromatic colors Fixes #516 --- src/spaces/okhsl.js | 5 +++++ src/spaces/okhsv.js | 5 +++++ test/conversions.js | 8 ++++---- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/spaces/okhsl.js b/src/spaces/okhsl.js index 4ba2d496f..1979a959e 100644 --- a/src/spaces/okhsl.js +++ b/src/spaces/okhsl.js @@ -458,6 +458,11 @@ function oklabToOkhsl (lab, lmsToRgb, okCoeff) { } } + if (Math.abs(s) < 1e-4 || l === 0.0 || Math.abs(1 - l) < 1e-7) { + h = NaN; + s = 0.0; + } + return [constrain(h * 360), s, l]; } diff --git a/src/spaces/okhsv.js b/src/spaces/okhsv.js index 58f0c804f..ad532346d 100644 --- a/src/spaces/okhsv.js +++ b/src/spaces/okhsv.js @@ -134,6 +134,11 @@ function oklabToOkhsv (lab, lmsToRgb, okCoeff) { s = (s0 + tMax) * cv / ((tMax * s0) + tMax * k * cv); } + if (Math.abs(s) < 1e-4 || v === 0.0) { + h = NaN; + s = 0.0; + } + return [constrain(h * 360), s, v]; } diff --git a/test/conversions.js b/test/conversions.js index 60ee5785b..55a3f8774 100644 --- a/test/conversions.js +++ b/test/conversions.js @@ -965,7 +965,7 @@ const tests = { { name: "sRGB white to Okhsl", args: "white", - expect: [180, 0.6519721306444567, 1.0000000000000002], + expect: [NaN, 0.0, 1.0000000000000002], }, { name: "sRGB red to Okhsl", @@ -1000,7 +1000,7 @@ const tests = { { name: "sRGB black to Okhsl", args: "black", - expect: [0.0, 0.0, 0.0], + expect: [NaN, 0.0, 0.0], }, ], }, @@ -1013,7 +1013,7 @@ const tests = { { name: "sRGB white to Okhsv", args: "white", - expect: [ 180, 1.3189507366749435e-15, 1.0000000000000007 ], + expect: [ NaN, 0.0, 1.0000000000000007 ], }, { name: "sRGB red to Okhsv", @@ -1048,7 +1048,7 @@ const tests = { { name: "sRGB black to Okhsv", args: "black", - expect: [0.0, 0.0, 0.0], + expect: [NaN, 0.0, 0.0], }, ], }, From 79007c5b5784e8d586f691f52e9d5f8e800882c9 Mon Sep 17 00:00:00 2001 From: facelessuser Date: Wed, 22 May 2024 16:11:24 -0600 Subject: [PATCH 2/3] Provide variables for epsilon values --- src/spaces/okhsl.js | 6 +++++- src/spaces/okhsv.js | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/spaces/okhsl.js b/src/spaces/okhsl.js index 1979a959e..614b40eab 100644 --- a/src/spaces/okhsl.js +++ b/src/spaces/okhsl.js @@ -423,6 +423,10 @@ function okhslToOklab (hsl, lmsToRgb, okCoeff) { function oklabToOkhsl (lab, lmsToRgb, okCoeff) { // Oklab to Okhsl. + // Epsilon for lightness should approach close to 32 bit lightness + // Epsilon for saturation just needs to be sufficiently close when denoting achromatic + let εL = 1e-7; + let εS = 1e-4; let L = lab[0]; let s = 0.0; let l = toe(L); @@ -458,7 +462,7 @@ function oklabToOkhsl (lab, lmsToRgb, okCoeff) { } } - if (Math.abs(s) < 1e-4 || l === 0.0 || Math.abs(1 - l) < 1e-7) { + if (Math.abs(s) < εS || l === 0.0 || Math.abs(1 - l) < εL) { h = NaN; s = 0.0; } diff --git a/src/spaces/okhsv.js b/src/spaces/okhsv.js index ad532346d..ad372aa07 100644 --- a/src/spaces/okhsv.js +++ b/src/spaces/okhsv.js @@ -95,6 +95,8 @@ function okhsvToOklab (hsv, lmsToRgb, okCoeff) { function oklabToOkhsv (lab, lmsToRgb, okCoeff) { // Oklab to Okhsv. + // Epsilon for saturation just needs to be sufficiently close when denoting achromatic + let ε = 1e-4; let l = lab[0]; let s = 0.0; let v = toe(l); @@ -134,7 +136,7 @@ function oklabToOkhsv (lab, lmsToRgb, okCoeff) { s = (s0 + tMax) * cv / ((tMax * s0) + tMax * k * cv); } - if (Math.abs(s) < 1e-4 || v === 0.0) { + if (Math.abs(s) < ε || v === 0.0) { h = NaN; s = 0.0; } From ce85731963a37da258e87dd6057e6135ed228b36 Mon Sep 17 00:00:00 2001 From: facelessuser Date: Mon, 27 May 2024 18:48:33 -0600 Subject: [PATCH 3/3] Update to use null and rework Okhsl saturation handling --- package-lock.json | 9 ++++----- src/spaces/okhsl.js | 22 ++++++++++++++++------ src/spaces/okhsv.js | 13 ++++++++----- test/conversions.js | 8 ++++---- 4 files changed, 32 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index c44a43cf8..47a0fc843 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "acorn": "latest", "core-js": "^3.36.0", "eslint": "latest", - "htest.dev": "^0.0", + "htest.dev": ">=0.0.14", "mathjs": "^12.4.0", "npm-run-all": "^4.1.5", "release-it": "^17.1.1", @@ -8203,11 +8203,10 @@ "license": "ISC" }, "node_modules/htest.dev": { - "version": "0.0.13", - "resolved": "https://registry.npmjs.org/htest.dev/-/htest.dev-0.0.13.tgz", - "integrity": "sha512-lu3ruqWMbPSujRf6Wm5QWjUi/mymfXinmxI1yp/S4WNnmF9kgbWLnUabUw9y+q1IcNHfAV8uZIEV2I2oViaKdg==", + "version": "0.0.14", + "resolved": "https://registry.npmjs.org/htest.dev/-/htest.dev-0.0.14.tgz", + "integrity": "sha512-G5kUQtGPIbnsAiBv4LOetX3hbsq08LlRLauQ6vt8l3An/zf+Bz1iUAJjuPAzJxyUFh728g9CQ2UY6PaeK+kRog==", "dev": true, - "license": "MIT", "dependencies": { "glob": "^10.3.10", "log-update": "^6.0.0", diff --git a/src/spaces/okhsl.js b/src/spaces/okhsl.js index 614b40eab..794765484 100644 --- a/src/spaces/okhsl.js +++ b/src/spaces/okhsl.js @@ -376,8 +376,8 @@ function okhslToOklab (hsl, lmsToRgb, okCoeff) { let [h, s, l] = hsl; let L = toeInv(l); - let a = 0.0; - let b = 0.0; + let a = null; + let b = null; h = constrain(h) / 360.0; if (L !== 0.0 && L !== 1.0 && s !== 0) { @@ -462,12 +462,22 @@ function oklabToOkhsl (lab, lmsToRgb, okCoeff) { } } - if (Math.abs(s) < εS || l === 0.0 || Math.abs(1 - l) < εL) { - h = NaN; - s = 0.0; + const achromatic = Math.abs(s) < εS; + if (achromatic || l === 0.0 || Math.abs(1 - l) < εL) { + h = null; + // Due to floating point imprecision near lightness of 1, we can end up + // with really high around white, this is to provide consistency as + // saturation can be really high for white due this imprecision. + if (!achromatic) { + s = 0.0; + } + } + + else { + h = constrain(h * 360); } - return [constrain(h * 360), s, l]; + return [h, s, l]; } diff --git a/src/spaces/okhsv.js b/src/spaces/okhsv.js index ad372aa07..6d9f6b6b2 100644 --- a/src/spaces/okhsv.js +++ b/src/spaces/okhsv.js @@ -47,8 +47,8 @@ function okhsvToOklab (hsv, lmsToRgb, okCoeff) { h = constrain(h) / 360.0; let l = toeInv(v); - let a = 0.0; - let b = 0.0; + let a = null; + let b = null; // Avoid processing gray or colors with undefined hues if (l !== 0.0 && s !== 0.0) { @@ -137,11 +137,14 @@ function oklabToOkhsv (lab, lmsToRgb, okCoeff) { } if (Math.abs(s) < ε || v === 0.0) { - h = NaN; - s = 0.0; + h = null; } - return [constrain(h * 360), s, v]; + else { + h = constrain(h * 360); + } + + return [h, s, v]; } diff --git a/test/conversions.js b/test/conversions.js index 55a3f8774..c21e6c1cc 100644 --- a/test/conversions.js +++ b/test/conversions.js @@ -965,7 +965,7 @@ const tests = { { name: "sRGB white to Okhsl", args: "white", - expect: [NaN, 0.0, 1.0000000000000002], + expect: [ null, 0.0, 1.0000000000000002 ], }, { name: "sRGB red to Okhsl", @@ -1000,7 +1000,7 @@ const tests = { { name: "sRGB black to Okhsl", args: "black", - expect: [NaN, 0.0, 0.0], + expect: [ null, 0.0, 0.0 ], }, ], }, @@ -1013,7 +1013,7 @@ const tests = { { name: "sRGB white to Okhsv", args: "white", - expect: [ NaN, 0.0, 1.0000000000000007 ], + expect: [ null, 1.3189507366749435e-15, 1.0000000000000007 ], }, { name: "sRGB red to Okhsv", @@ -1048,7 +1048,7 @@ const tests = { { name: "sRGB black to Okhsv", args: "black", - expect: [NaN, 0.0, 0.0], + expect: [ null, 0.0, 0.0], }, ], },