Skip to content

Commit

Permalink
Clamp chroma in hcl.toString.
Browse files Browse the repository at this point in the history
  • Loading branch information
mbostock committed Jun 27, 2019
1 parent 43f5ab6 commit 1f8efbb
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 15 deletions.
39 changes: 30 additions & 9 deletions src/lab.js
Expand Up @@ -2,23 +2,20 @@ import define, {extend} from "./define";
import {Color, rgbConvert, Rgb} from "./color";
import {deg2rad, rad2deg} from "./math";

// https://beta.observablehq.com/@mbostock/lab-and-rgb
// https://observablehq.com/@mbostock/lab-and-rgb
var K = 18,
Xn = 0.96422,
Yn = 1,
Zn = 0.82521,
t0 = 4 / 29,
t1 = 6 / 29,
t2 = 3 * t1 * t1,
t3 = t1 * t1 * t1;
t3 = t1 * t1 * t1,
dc = 0.1;

function labConvert(o) {
if (o instanceof Lab) return new Lab(o.l, o.a, o.b, o.opacity);
if (o instanceof Hcl) {
if (isNaN(o.h)) return new Lab(o.l, 0, 0, o.opacity);
var h = o.h * deg2rad;
return new Lab(o.l, Math.cos(h) * o.c, Math.sin(h) * o.c, o.opacity);
}
if (o instanceof Hcl) return hcl2lab(o);
if (!(o instanceof Rgb)) o = rgbConvert(o);
var r = rgb2lrgb(o.r),
g = rgb2lrgb(o.g),
Expand Down Expand Up @@ -88,7 +85,7 @@ function rgb2lrgb(x) {
function hclConvert(o) {
if (o instanceof Hcl) return new Hcl(o.h, o.c, o.l, o.opacity);
if (!(o instanceof Lab)) o = labConvert(o);
if (o.a === 0 && o.b === 0) return new Hcl(NaN, 0 < o.l ? 0 : NaN, o.l, o.opacity);
if (o.a === 0 && o.b === 0) return new Hcl(NaN, 0 < o.l && o.l < 100 ? 0 : NaN, o.l, o.opacity);
var h = Math.atan2(o.b, o.a) * rad2deg;
return new Hcl(h < 0 ? h + 360 : h, Math.sqrt(o.a * o.a + o.b * o.b), o.l, o.opacity);
}
Expand All @@ -108,6 +105,16 @@ export function Hcl(h, c, l, opacity) {
this.opacity = +opacity;
}

function hcl2lab(o) {
if (isNaN(o.h)) return new Lab(o.l, 0, 0, o.opacity);
var h = o.h * deg2rad;
return new Lab(o.l, Math.cos(h) * o.c, Math.sin(h) * o.c, o.opacity);
}

function hcl2rgb(o) {
return hcl2lab(o).rgb();
}

define(Hcl, hcl, extend(Color, {
brighter: function(k) {
return new Hcl(this.h, this.c, this.l + K * (k == null ? 1 : k), this.opacity);
Expand All @@ -116,6 +123,20 @@ define(Hcl, hcl, extend(Color, {
return new Hcl(this.h, this.c, this.l - K * (k == null ? 1 : k), this.opacity);
},
rgb: function() {
return labConvert(this).rgb();
return hcl2rgb(this);
},
toString: function() {
if ((rgb = hcl2rgb(this)).displayable()) return rgb + "";
var c0, c1, rgb, hcl = new Hcl(this.h, 0, this.l, 1);
if ((rgb = hcl2rgb(hcl)).displayable()) {
c0 = 0, c1 = this.c;
while (c1 - c0 > dc) {
hcl.c = (c0 + c1) * 0.5;
if ((rgb = hcl2rgb(hcl)).displayable()) c0 = hcl.c;
else c1 = hcl.c;
}
}
rgb.opacity = this.opacity;
return rgb + "";
}
}));
18 changes: 12 additions & 6 deletions test/hcl-test.js
Expand Up @@ -16,17 +16,17 @@ tape("hcl(…) exposes h, c, and l channel values", function(test) {
test.end();
});

tape("hcl(…) returns defined hue and undefined chroma for black", function(test) {
tape("hcl(…) returns defined hue and undefined chroma for black and white", function(test) {
test.hclEqual(color.hcl("black"), NaN, NaN, 0, 1);
test.hclEqual(color.hcl("#000"), NaN, NaN, 0, 1);
test.hclEqual(color.hcl(color.lab("#000")), NaN, NaN, 0, 1);
test.hclEqual(color.hcl("white"), NaN, NaN, 100, 1);
test.hclEqual(color.hcl("#fff"), NaN, NaN, 100, 1);
test.hclEqual(color.hcl(color.lab("#fff")), NaN, NaN, 100, 1);
test.end();
});

tape("hcl(…) returns defined hue and chroma for gray and white", function(test) {
test.hclEqual(color.hcl("white"), NaN, 0, 100, 1);
test.hclEqual(color.hcl("#fff"), NaN, 0, 100, 1);
test.hclEqual(color.hcl(color.lab("#fff")), NaN, 0, 100, 1);
tape("hcl(…) returns undefined hue and zero chroma for gray", function(test) {
test.hclEqual(color.hcl("gray"), NaN, 0, 53.585013, 1);
test.hclEqual(color.hcl(color.lab("gray")), NaN, 0, 53.585013, 1);
test.end();
Expand Down Expand Up @@ -70,6 +70,12 @@ tape("hcl.toString() treats undefined channel values as 0", function(test) {
test.end();
});

tape("hcl.toString() clamps chroma", function(test) {
test.equal(color.hcl(302, 130, 0, 0.4) + "", "rgba(0, 0, 0, 0.4)");
test.equal(color.hcl(302, 130, 100, 0.4) + "", "rgba(255, 255, 255, 0.4)");
test.end();
});

tape("hcl(h, c, l) does not wrap hue to [0,360)", function(test) {
test.hclEqual(color.hcl(-10, 40, 50), -10, 40, 50, 1);
test.hclEqual(color.hcl(0, 40, 50), 0, 40, 50, 1);
Expand Down Expand Up @@ -146,7 +152,7 @@ tape("hcl(hcl) copies an HCL color", function(test) {
tape("hcl(lab) returns h = NaN if a and b are zero", function(test) {
test.hclEqual(color.hcl(color.lab(0, 0, 0)), NaN, NaN, 0, 1);
test.hclEqual(color.hcl(color.lab(50, 0, 0)), NaN, 0, 50, 1);
test.hclEqual(color.hcl(color.lab(100, 0, 0)), NaN, 0, 100, 1);
test.hclEqual(color.hcl(color.lab(100, 0, 0)), NaN, NaN, 100, 1);
test.hclEqual(color.hcl(color.lab(0, 10, 0)), 0, 10, 0, 1);
test.hclEqual(color.hcl(color.lab(50, 10, 0)), 0, 10, 50, 1);
test.hclEqual(color.hcl(color.lab(100, 10, 0)), 0, 10, 100, 1);
Expand Down

0 comments on commit 1f8efbb

Please sign in to comment.