diff --git a/src/color.js b/src/color.js index 3af25a9..6552e3d 100644 --- a/src/color.js +++ b/src/color.js @@ -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) @@ -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); } @@ -347,25 +354,29 @@ define(Hsl, hsl, extend(Color, { this.opacity ); }, + clamp: function() { + return new Hsl(clamph(this.h), clampt(this.s), clampt(this.l), clampa(this.opacity)); + }, 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 diff --git a/test/hsl-test.js b/test/hsl-test.js index d963916..930092d 100644 --- a/test/hsl-test.js +++ b/test/hsl-test.js @@ -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); diff --git a/test/rgb-test.js b/test/rgb-test.js index 32ebdf2..158a841 100644 --- a/test/rgb-test.js +++ b/test/rgb-test.js @@ -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);