| "use strict"; |
| |
| // these algorithms are sourced from https://drafts.csswg.org/css-color/#color-conversion-code |
| |
| function lin_sRGB(RGB) { |
| // convert an array of sRGB values in the range 0.0 - 1.0 |
| // to linear light (un-companded) form. |
| // https://en.wikipedia.org/wiki/SRGB |
| return RGB.map(function(val) { |
| if (val < 0.04045) { |
| return val / 12.92; |
| } |
| |
| return Math.pow((val + 0.055) / 1.055, 2.4); |
| }); |
| } |
| |
| function matrixMultiple3d(matrix, vector) { |
| return [ |
| matrix[0][0] * vector[0] + |
| matrix[0][1] * vector[1] + |
| matrix[0][2] * vector[2], |
| matrix[1][0] * vector[0] + |
| matrix[1][1] * vector[1] + |
| matrix[1][2] * vector[2], |
| matrix[2][0] * vector[0] + |
| matrix[2][1] * vector[1] + |
| matrix[2][2] * vector[2] |
| ]; |
| } |
| |
| function srgb2xyz(srgb) { |
| return matrixMultiple3d( |
| [ |
| [0.4124564, 0.3575761, 0.1804375], |
| [0.2126729, 0.7151522, 0.072175], |
| [0.0193339, 0.119192, 0.9503041] |
| ], |
| srgb |
| ); |
| } |
| |
| function chromaticAdaptationD65_D50(xyz) { |
| return matrixMultiple3d( |
| [ |
| [1.0478112, 0.0228866, -0.050127], |
| [0.0295424, 0.9904844, -0.0170491], |
| [-0.0092345, 0.0150436, 0.7521316] |
| ], |
| xyz |
| ); |
| } |
| |
| function xyz2lab(xyzIn) { |
| // Assuming XYZ is relative to D50, convert to CIE Lab |
| // from CIE standard, which now defines these as a rational fraction |
| const ε = 216 / 24389; // 6^3/29^3 |
| const κ = 24389 / 27; // 29^3/3^3 |
| const white = [0.9642, 1.0, 0.8249]; // D50 reference white |
| |
| // compute xyz, which is XYZ scaled relative to reference white |
| const xyz = xyzIn.map((value, i) => value / white[i]); |
| |
| // now compute f |
| const f = xyz.map(value => |
| value > ε ? Math.cbrt(value) : (κ * value + 16) / 116 |
| ); |
| |
| return [ |
| 116 * f[1] - 16, // L |
| 500 * (f[0] - f[1]), // a |
| 200 * (f[1] - f[2]) // b |
| ]; |
| } |
| |
| function rgb2hsl(r, g, b) { |
| r /= 255; |
| g /= 255; |
| b /= 255; |
| let h; |
| let s; |
| let l; |
| const M = Math.max(r, g, b); |
| const m = Math.min(r, g, b); |
| const d = M - m; |
| |
| if (d === 0) { |
| h = 0; |
| } else if (M === r) { |
| h = ((g - b) / d) % 6; |
| } else if (M === g) { |
| h = (b - r) / d + 2; |
| } else { |
| h = (r - g) / d + 4; |
| } |
| |
| h *= 60; |
| |
| if (h < 0) { |
| h += 360; |
| } |
| |
| l = (M + m) / 2; |
| |
| if (d === 0) { |
| s = 0; |
| } else { |
| s = d / (1 - Math.abs(2 * l - 1)); |
| } |
| |
| s *= 100; |
| l *= 100; |
| |
| return [Math.round(h), Math.round(s), Math.round(l)]; |
| } |
| |
| function rgb2hwb(rgb_r, rgb_g, rgb_b) { |
| rgb_r /= 255; |
| rgb_g /= 255; |
| rgb_b /= 255; |
| |
| const w = Math.min(rgb_r, rgb_g, rgb_b); |
| const v = Math.max(rgb_r, rgb_g, rgb_b); |
| |
| const b = 1 - v; |
| |
| if (v === w) { |
| return [0, Math.round(w * 100), Math.round(b * 100)]; |
| } |
| |
| const f = |
| rgb_r === w ? rgb_g - rgb_b : rgb_g === w ? rgb_b - rgb_r : rgb_r - rgb_g; |
| const i = rgb_r === w ? 3 : rgb_g === w ? 5 : 1; |
| |
| return [ |
| Math.round(((i - f / (v - w)) / 6) * 360) % 360, |
| Math.round(w * 100), |
| Math.round(b * 100) |
| ]; |
| } |
| |
| function perc255(value) { |
| return Math.round((value * 100) / 255) + "%"; |
| } |
| |
| function generateColorFuncs(hexString) { |
| if (hexString.length !== 7) { |
| throw new Error( |
| "Invalid hex string color definition (" + |
| hexString + |
| ") - expected 6 character hex string" |
| ); |
| } |
| |
| const rgb = [0, 0, 0]; |
| |
| for (let i = 0; i < 3; i += 1) { |
| rgb[i] = parseInt(hexString.substr(2 * i + 1, 2), 16); |
| } |
| |
| const hsl = rgb2hsl(rgb[0], rgb[1], rgb[2]); |
| const hwb = rgb2hwb(rgb[0], rgb[1], rgb[2]); |
| const func = []; |
| const rgbStr = rgb[0] + "," + rgb[1] + "," + rgb[2]; |
| const rgbPercStr = |
| perc255(rgb[0]) + "," + perc255(rgb[1]) + "," + perc255(rgb[2]); |
| const hslStr = hsl[0] + "," + hsl[1] + "%," + hsl[2] + "%"; |
| const hwbStr = hwb[0] + "," + hwb[1] + "%," + hwb[2] + "%"; |
| |
| // *very* convoluted process, just to be able to establish if the color |
| // is gray -- or not. |
| const linRgb = lin_sRGB([rgb[0] / 255, rgb[1] / 255, rgb[2] / 255]); |
| const xyz_d65 = srgb2xyz(linRgb); |
| const xyz_d50 = chromaticAdaptationD65_D50(xyz_d65); |
| const lab = xyz2lab(xyz_d50); |
| |
| func.push("rgb(" + rgbStr + ")"); |
| func.push("rgba(" + rgbStr + ",1)"); |
| func.push("rgba(" + rgbStr + ",100%)"); |
| func.push("rgb(" + rgbPercStr + ")"); |
| func.push("rgba(" + rgbPercStr + ",1)"); |
| func.push("rgba(" + rgbPercStr + ",100%)"); |
| func.push("hsl(" + hslStr + ")"); |
| func.push("hsla(" + hslStr + ",1)"); |
| func.push("hsla(" + hslStr + ",100%)"); |
| func.push("hwb(" + hwbStr + ")"); |
| func.push("hwb(" + hwbStr + ",1)"); |
| func.push("hwb(" + hwbStr + ",100%)"); |
| |
| // technically, this should be 0 - but then #808080 wouldn't even be gray |
| if (lab[1] * lab[1] < 0.01 && lab[2] * lab[2] < 0.01) { |
| // yay! gray! |
| const grayStr = Math.round(lab[0]); |
| |
| func.push("gray(" + grayStr + ")"); |
| func.push("gray(" + grayStr + ",1)"); |
| func.push("gray(" + grayStr + ",100%)"); |
| func.push("gray(" + grayStr + "%)"); |
| func.push("gray(" + grayStr + "%,1)"); |
| func.push("gray(" + grayStr + "%,100%)"); |
| } |
| |
| return func; |
| } |
| |
| module.exports = generateColorFuncs; |