From 074dd6dc20be31616b137336404a74c619669603 Mon Sep 17 00:00:00 2001 From: Antoine Cottineau <61667876+antoine-cottineau@users.noreply.github.com> Date: Fri, 11 Aug 2023 09:45:51 +0200 Subject: [PATCH] Support HSL colors (#679) * refactor: split rgb parsing and convertion in two functions This will be useful for the following as we will need a function to convert from hsl to hex. To do that, we'll need a function to convert from hsl to rgb and then from rgb to hex. So, this commit helps us reuse the code for doing the last step. * feat: add support for hsl colors * refactor: rename test file --- ...a2hex.test.js => colorConversions.test.js} | 11 +++ src/colorToHex.js | 81 ++++++++++++++++--- 2 files changed, 81 insertions(+), 11 deletions(-) rename src/{rgba2hex.test.js => colorConversions.test.js} (68%) diff --git a/src/rgba2hex.test.js b/src/colorConversions.test.js similarity index 68% rename from src/rgba2hex.test.js rename to src/colorConversions.test.js index 8f2eeb3..5ff6c0e 100644 --- a/src/rgba2hex.test.js +++ b/src/colorConversions.test.js @@ -40,4 +40,15 @@ describe('colorToHex', () => { it('return undefined when undefined', () => { expect(colorToHex(undefined)).toEqual(undefined) }) + + it('should transform hsl to hex', () => { + expect(colorToHex('hsl(0, 0%, 0%)')).toEqual('#000000') + expect(colorToHex('hsl(0, 0%, 100%)')).toEqual('#ffffff') + expect(colorToHex('hsl(0, 0%, 50%)')).toEqual('#808080') + expect(colorToHex('hsl(34, 87%, 50%)')).toEqual('#ee8e11') + expect(colorToHex('hsl(137, 83%, 71%)')).toEqual('#78f29a') + expect(colorToHex('hsl(360 100% 100%)')).toEqual('#ffffff') + expect(colorToHex('hsla(137, 83%, 71%, 0.5)')).toEqual('#78f29a') + expect(colorToHex('hsla(360 100% 100% / 1.0)')).toEqual('#ffffff') + }) }) diff --git a/src/colorToHex.js b/src/colorToHex.js index 18391a5..dbf054e 100644 --- a/src/colorToHex.js +++ b/src/colorToHex.js @@ -1,7 +1,8 @@ export function colorToHex(c) { if (c === undefined) return c - if (c === "none") return c - if (c.includes('rgb')) return rgb2hex(c) + if (c === 'none') return c + if (c.includes('rgb')) return rgb2hex(parseRgb(c)) + if (c.includes('hsl')) return hsl2hex(parseHsl(c)) if (c.includes('#')) { if (!isValidHex(c)) throw Error('Invalid color: ' + c) if (c.length === 4) return '#' + c[1] + c[1] + c[2] + c[2] + c[3] + c[3] @@ -16,16 +17,74 @@ function isValidHex(color) { return /^#([0-9A-Fa-f]{3}){1,2}$/i.test(color) } -function rgb2hex(rgb) { - rgb = rgb.match( - /^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i +function parseRgb(rgb) { + return rgb + .match( + /^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i + ) + .slice(1) +} + +function rgb2hex(rgbArray) { + if (rgbArray.length != 3) { + return '' + } + return ( + '#' + + parseInt(rgbArray[0]).toString(16).padStart(2, '0') + + parseInt(rgbArray[1]).toString(16).padStart(2, '0') + + parseInt(rgbArray[2]).toString(16).padStart(2, '0') ) - return rgb && rgb.length === 4 - ? '#' + - ('0' + parseInt(rgb[1], 10).toString(16)).slice(-2) + - ('0' + parseInt(rgb[2], 10).toString(16)).slice(-2) + - ('0' + parseInt(rgb[3], 10).toString(16)).slice(-2) - : '' +} + +function parseHsl(hsl) { + const matches = hsl.match( + /^hsla*\((\d{1,3})\s*[, ]\s*(\d{1,3})%\s*[, ]\s*(\d{1,3})%.*\)/i + ) + + if (matches === null) { + throw Error('Invalid color: ' + hsl) + } + + const h = matches[1] / 360 + const s = matches[2] / 100 + const l = matches[3] / 100 + return [h, s, l] +} + +function hsl2hex(hslArray) { + if (hslArray.length != 3) { + return '' + } + + const [h, s, l] = hslArray + + var r, g, b + + if (s === 0) { + r = g = b = l + } else { + const hue2rgb = (p, q, t) => { + if (t < 0) t += 1 + if (t > 1) t -= 1 + if (t < 1 / 6) return p + (q - p) * 6 * t + if (t < 1 / 2) return q + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6 + return p + } + + const q = l < 0.5 ? l * (1 + s) : l + s - l * s + const p = 2 * l - q + r = hue2rgb(p, q, h + 1 / 3) + g = hue2rgb(p, q, h) + b = hue2rgb(p, q, h - 1 / 3) + } + + return rgb2hex([ + Math.round(r * 255), + Math.round(g * 255), + Math.round(b * 255), + ]) } function colourNameToHex(color) {