Skip to content

Commit

Permalink
{rgb,hsl}.clamp()
Browse files Browse the repository at this point in the history
  • Loading branch information
mbostock committed Mar 28, 2022
1 parent 70e3a04 commit 5061ec1
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 17 deletions.
49 changes: 32 additions & 17 deletions src/color.js
Expand Up @@ -251,6 +251,9 @@ define(Rgb, rgb, extend(Color, {
rgb: function() {
return this;
},
clamp: function() {
return new Rgb(clampi(this.r), clampi(this.g), clampi(this.b), clampa(this.opacity));
},
displayable: function() {
return (-0.5 <= this.r && this.r < 255.5)
&& (-0.5 <= this.g && this.g < 255.5)
Expand All @@ -268,16 +271,20 @@ function rgb_formatHex() {
}

function rgb_formatRgb() {
var a = this.opacity; a = isNaN(a) ? 1 : Math.max(0, Math.min(1, a));
return (a === 1 ? "rgb(" : "rgba(")
+ Math.max(0, Math.min(255, Math.round(this.r) || 0)) + ", "
+ Math.max(0, Math.min(255, Math.round(this.g) || 0)) + ", "
+ Math.max(0, Math.min(255, Math.round(this.b) || 0))
+ (a === 1 ? ")" : ", " + a + ")");
const a = clampa(this.opacity);
return `${a === 1 ? "rgb(" : "rgba("}${clampi(this.r)}, ${clampi(this.g)}, ${clampi(this.b)}${a === 1 ? ")" : `, ${a})`}`;
}

function clampa(opacity) {
return isNaN(opacity) ? 1 : Math.max(0, Math.min(1, opacity));
}

function clampi(value) {
return Math.max(0, Math.min(255, Math.round(value) || 0));
}

function hex(value) {
value = Math.max(0, Math.min(255, Math.round(value) || 0));
value = clampi(value);
return (value < 16 ? "0" : "") + value.toString(16);
}

Expand Down Expand Up @@ -347,25 +354,33 @@ define(Hsl, hsl, extend(Color, {
this.opacity
);
},
clamp: function() {
var a = this.opacity,
h = (this.h || 0) % 360,
s = Math.max(0, Math.min(1, this.s || 0)),
l = Math.max(0, Math.min(1, this.l || 0));
return new Hsl(h < 0 ? h + 360 : h, s, l, isNaN(a) ? 1 : Math.max(0, Math.min(1, a)));
},
displayable: function() {
return (0 <= this.s && this.s <= 1 || isNaN(this.s))
&& (0 <= this.l && this.l <= 1)
&& (0 <= this.opacity && this.opacity <= 1);
},
formatHsl: function() {
var a = this.opacity,
h = (this.h || 0) % 360,
s = Math.max(0, Math.min(1, this.s || 0)),
l = Math.max(0, Math.min(1, this.l || 0));
a = isNaN(a) ? 1 : Math.max(0, Math.min(1, a));
return (a === 1 ? "hsl(" : "hsla(")
+ (h < 0 ? h + 360 : h) + ", "
+ s * 100 + "%, "
+ l * 100 + "%"
+ (a === 1 ? ")" : ", " + a + ")");
const a = clampa(this.opacity);
return `${a === 1 ? "hsl(" : "hsla("}${clamph(this.h)}, ${clampt(this.s) * 100}%, ${clampt(this.l) * 100}%${a === 1 ? ")" : `, ${a})`}`;
}
}));

function clamph(value) {
value = (value || 0) % 360;
return value < 0 ? value + 360 : value;
}

function clampt(value) {
return Math.max(0, Math.min(1, value || 0));
}

/* From FvD 13.37, CSS Color Module Level 3 */
function hsl2rgb(h, m1, m2) {
return (h < 60 ? m1 + (m2 - m1) * h / 60
Expand Down
12 changes: 12 additions & 0 deletions test/hsl-test.js
Expand Up @@ -89,6 +89,18 @@ it("hsl(h, s, l) does not clamp s and l channel values to [0,1]", () => {
assertHslEqual(hsl(120, 0.2, 1.1), 120, 0.2, 1.1, 1);
});

it("hsl(h, s, l).clamp() clamps channel values", () => {
assertHslEqual(hsl(120, -0.1, -0.2).clamp(), 120, 0, 0, 1);
assertHslEqual(hsl(120, 1.1, 1.2).clamp(), 120, 1, 1, 1);
assertHslEqual(hsl(120, 2.1, 2.2).clamp(), 120, 1, 1, 1);
assertHslEqual(hsl(420, -0.1, -0.2).clamp(), 60, 0, 0, 1);
assertHslEqual(hsl(-420, -0.1, -0.2).clamp(), 300, 0, 0, 1);
assert.strictEqual(hsl(-420, -0.1, -0.2, NaN).clamp().opacity, 1);
assert.strictEqual(hsl(-420, -0.1, -0.2, 0.5).clamp().opacity, 0.5);
assert.strictEqual(hsl(-420, -0.1, -0.2, -1).clamp().opacity, 0);
assert.strictEqual(hsl(-420, -0.1, -0.2, 2).clamp().opacity, 1);
});

it("hsl(h, s, l, opacity) does not clamp opacity to [0,1]", () => {
assertHslEqual(hsl(120, 0.1, 0.5, -0.2), 120, 0.1, 0.5, -0.2);
assertHslEqual(hsl(120, 0.9, 0.5, 1.2), 120, 0.9, 0.5, 1.2);
Expand Down
10 changes: 10 additions & 0 deletions test/rgb-test.js
Expand Up @@ -91,6 +91,16 @@ it("rgb(r, g, b) does not clamp channel values", () => {
assertRgbApproxEqual(rgb(300, 400, 500), 300, 400, 500, 1);
});

it("rgb(r, g, b).clamp() rounds and clamps channel values", () => {
assertRgbApproxEqual(rgb(-10, -20, -30).clamp(), 0, 0, 0, 1);
assertRgbApproxEqual(rgb(10.5, 20.5, 30.5).clamp(), 11, 21, 31, 1);
assertRgbApproxEqual(rgb(300, 400, 500).clamp(), 255, 255, 255, 1);
assert.strictEqual(rgb(10.5, 20.5, 30.5, -1).clamp().opacity, 0);
assert.strictEqual(rgb(10.5, 20.5, 30.5, 0.5).clamp().opacity, 0.5);
assert.strictEqual(rgb(10.5, 20.5, 30.5, 2).clamp().opacity, 1);
assert.strictEqual(rgb(10.5, 20.5, 30.5, NaN).clamp().opacity, 1);
});

it("rgb(r, g, b, opacity) does not clamp opacity", () => {
assertRgbApproxEqual(rgb(-10, -20, -30, -0.2), -10, -20, -30, -0.2);
assertRgbApproxEqual(rgb(300, 400, 500, 1.2), 300, 400, 500, 1.2);
Expand Down

0 comments on commit 5061ec1

Please sign in to comment.