Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clamp chroma in hcl.toString. #57

Merged
merged 1 commit into from Jun 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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