diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4efab95..0b7b78f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,20 +14,14 @@ jobs: strategy: matrix: node-version: - - 7 - - 8 - - 9 - - 10 - - 11 - - 13 - - 14 - - 15 + - 20 + - 18 + - 16 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - run: npm install - - run: npm run pretest - run: npm test diff --git a/README.md b/README.md index 31b4362..7095b94 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ const ansiChannels = convert.ansi16.channels; // 1 # Install ```sh -$ npm install color-convert +npm install color-convert ``` # API @@ -117,7 +117,7 @@ channel | full-scale value ### gray channel | full-scale value ---|--- -g | 100 +gray | 100 # Contribute diff --git a/conversions.js b/conversions.js index 5cb9c73..0d64885 100644 --- a/conversions.js +++ b/conversions.js @@ -1,6 +1,6 @@ /* MIT license */ /* eslint-disable no-mixed-operators */ -const cssKeywords = require('color-name'); +import cssKeywords from 'color-name'; // NOTE: conversions should only return primitive values (i.e. arrays, or // values that give correct `typeof` results). @@ -26,13 +26,13 @@ const convert = { ansi256: {channels: 1, labels: ['ansi256']}, hcg: {channels: 3, labels: ['h', 'c', 'g']}, apple: {channels: 3, labels: ['r16', 'g16', 'b16']}, - gray: {channels: 1, labels: ['gray']} + gray: {channels: 1, labels: ['gray']}, }; -module.exports = convert; +export default convert; // LAB f(t) constant -const LAB_FT = Math.pow(6 / 29, 3); +const LAB_FT = (6 / 29) ** 3; // Hide .channels and .labels properties for (const model of Object.keys(convert)) { @@ -65,14 +65,31 @@ convert.rgb.hsl = function (rgb) { let h; let s; - if (max === min) { - h = 0; - } else if (r === max) { - h = (g - b) / delta; - } else if (g === max) { - h = 2 + (b - r) / delta; - } else if (b === max) { - h = 4 + (r - g) / delta; + switch (max) { + case min: { + h = 0; + + break; + } + + case r: { + h = (g - b) / delta; + + break; + } + + case g: { + h = 2 + (b - r) / delta; + + break; + } + + case b: { + h = 4 + (r - g) / delta; + + break; + } + // No default } h = Math.min(h * 60, 360); @@ -119,12 +136,25 @@ convert.rgb.hsv = function (rgb) { gdif = diffc(g); bdif = diffc(b); - if (r === v) { - h = bdif - gdif; - } else if (g === v) { - h = (1 / 3) + rdif - bdif; - } else if (b === v) { - h = (2 / 3) + gdif - rdif; + switch (v) { + case r: { + h = bdif - gdif; + + break; + } + + case g: { + h = (1 / 3) + rdif - bdif; + + break; + } + + case b: { + h = (2 / 3) + gdif - rdif; + + break; + } + // No default } if (h < 0) { @@ -137,7 +167,7 @@ convert.rgb.hsv = function (rgb) { return [ h * 360, s * 100, - v * 100 + v * 100, ]; }; @@ -183,7 +213,7 @@ convert.rgb.keyword = function (rgb) { return reversed; } - let currentClosestDistance = Infinity; + let currentClosestDistance = Number.POSITIVE_INFINITY; let currentClosestKeyword; for (const keyword of Object.keys(cssKeywords)) { @@ -212,13 +242,13 @@ convert.rgb.xyz = function (rgb) { let b = rgb[2] / 255; // Assume sRGB - r = r > 0.04045 ? (((r + 0.055) / 1.055) ** 2.4) : (r / 12.92); - g = g > 0.04045 ? (((g + 0.055) / 1.055) ** 2.4) : (g / 12.92); - b = b > 0.04045 ? (((b + 0.055) / 1.055) ** 2.4) : (b / 12.92); + r = r > 0.040_45 ? (((r + 0.055) / 1.055) ** 2.4) : (r / 12.92); + g = g > 0.040_45 ? (((g + 0.055) / 1.055) ** 2.4) : (g / 12.92); + b = b > 0.040_45 ? (((b + 0.055) / 1.055) ** 2.4) : (b / 12.92); - const x = (r * 0.4124564) + (g * 0.3575761) + (b * 0.1804375); - const y = (r * 0.2126729) + (g * 0.7151522) + (b * 0.072175); - const z = (r * 0.0193339) + (g * 0.119192) + (b * 0.9503041); + const x = (r * 0.412_456_4) + (g * 0.357_576_1) + (b * 0.180_437_5); + const y = (r * 0.212_672_9) + (g * 0.715_152_2) + (b * 0.072_175); + const z = (r * 0.019_333_9) + (g * 0.119_192) + (b * 0.950_304_1); return [x * 100, y * 100, z * 100]; }; @@ -248,20 +278,15 @@ convert.hsl.rgb = function (hsl) { const h = hsl[0] / 360; const s = hsl[1] / 100; const l = hsl[2] / 100; - let t2; let t3; - let val; + let value; if (s === 0) { - val = l * 255; - return [val, val, val]; + value = l * 255; + return [value, value, value]; } - if (l < 0.5) { - t2 = l * (1 + s); - } else { - t2 = l + s - l * s; - } + const t2 = l < 0.5 ? l * (1 + s) : l + s - l * s; const t1 = 2 * l - t2; @@ -277,16 +302,16 @@ convert.hsl.rgb = function (hsl) { } if (6 * t3 < 1) { - val = t1 + (t2 - t1) * 6 * t3; + value = t1 + (t2 - t1) * 6 * t3; } else if (2 * t3 < 1) { - val = t2; + value = t2; } else if (3 * t3 < 2) { - val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; + value = t1 + (t2 - t1) * (2 / 3 - t3) * 6; } else { - val = t1; + value = t1; } - rgb[i] = val * 255; + rgb[i] = value * 255; } return rgb; @@ -321,18 +346,29 @@ convert.hsv.rgb = function (hsv) { v *= 255; switch (hi) { - case 0: + case 0: { return [v, t, p]; - case 1: + } + + case 1: { return [q, v, p]; - case 2: + } + + case 2: { return [p, v, t]; - case 3: + } + + case 3: { return [p, q, v]; - case 4: + } + + case 4: { return [t, p, v]; - case 5: + } + + case 5: { return [v, p, q]; + } } }; @@ -372,6 +408,7 @@ convert.hwb.rgb = function (hwb) { const v = 1 - bl; f = 6 * h - i; + // eslint-disable-next-line no-bitwise if ((i & 0x01) !== 0) { f = 1 - f; } @@ -381,18 +418,29 @@ convert.hwb.rgb = function (hwb) { let r; let g; let b; - /* eslint-disable max-statements-per-line,no-multi-spaces */ + /* eslint-disable max-statements-per-line,no-multi-spaces, default-case-last */ switch (i) { default: case 6: - case 0: r = v; g = n; b = wh; break; - case 1: r = n; g = v; b = wh; break; - case 2: r = wh; g = v; b = n; break; - case 3: r = wh; g = n; b = v; break; - case 4: r = n; g = wh; b = v; break; - case 5: r = v; g = wh; b = n; break; + case 0: { r = v; g = n; b = wh; break; + } + + case 1: { r = n; g = v; b = wh; break; + } + + case 2: { r = wh; g = v; b = n; break; + } + + case 3: { r = wh; g = n; b = v; break; + } + + case 4: { r = n; g = wh; b = v; break; + } + + case 5: { r = v; g = wh; b = n; break; + } } - /* eslint-enable max-statements-per-line,no-multi-spaces */ + /* eslint-enable max-statements-per-line,no-multi-spaces, default-case-last */ return [r * 255, g * 255, b * 255]; }; @@ -418,21 +466,21 @@ convert.xyz.rgb = function (xyz) { let g; let b; - r = (x * 3.2404542) + (y * -1.5371385) + (z * -0.4985314); - g = (x * -0.969266) + (y * 1.8760108) + (z * 0.041556); - b = (x * 0.0556434) + (y * -0.2040259) + (z * 1.0572252); + r = (x * 3.240_454_2) + (y * -1.537_138_5) + (z * -0.498_531_4); + g = (x * -0.969_266) + (y * 1.876_010_8) + (z * 0.041_556); + b = (x * 0.055_643_4) + (y * -0.204_025_9) + (z * 1.057_225_2); // Assume sRGB - r = r > 0.0031308 - ? ((1.055 * (r ** (1.0 / 2.4))) - 0.055) + r = r > 0.003_130_8 + ? ((1.055 * (r ** (1 / 2.4))) - 0.055) : r * 12.92; - g = g > 0.0031308 - ? ((1.055 * (g ** (1.0 / 2.4))) - 0.055) + g = g > 0.003_130_8 + ? ((1.055 * (g ** (1 / 2.4))) - 0.055) : g * 12.92; - b = b > 0.0031308 - ? ((1.055 * (b ** (1.0 / 2.4))) - 0.055) + b = b > 0.003_130_8 + ? ((1.055 * (b ** (1 / 2.4))) - 0.055) : b * 12.92; r = Math.min(Math.max(0, r), 1); @@ -531,9 +579,11 @@ convert.rgb.ansi16 = function (args, saturation = null) { } let ansi = 30 + /* eslint-disable no-bitwise */ + ((Math.round(b / 255) << 2) | (Math.round(g / 255) << 1) | Math.round(r / 255)); + /* eslint-enable no-bitwise */ if (value === 2) { ansi += 60; @@ -555,6 +605,7 @@ convert.rgb.ansi256 = function (args) { // We use the extended greyscale palette here, with the exception of // black and white. normal palette only has 4 greyscale shades. + // eslint-disable-next-line no-bitwise if (r >> 4 === g >> 4 && g >> 4 === b >> 4) { if (r < 8) { return 16; @@ -591,10 +642,12 @@ convert.ansi16.rgb = function (args) { return [color, color, color]; } - const mult = (~~(args > 50) + 1) * 0.5; + const mult = (Math.trunc(args > 50) + 1) * 0.5; + /* eslint-disable no-bitwise */ const r = ((color & 1) * mult) * 255; const g = (((color >> 1) & 1) * mult) * 255; const b = (((color >> 2) & 1) * mult) * 255; + /* eslint-enable no-bitwise */ return [r, g, b]; }; @@ -619,16 +672,18 @@ convert.ansi256.rgb = function (args) { }; convert.rgb.hex = function (args) { + /* eslint-disable no-bitwise */ const integer = ((Math.round(args[0]) & 0xFF) << 16) + ((Math.round(args[1]) & 0xFF) << 8) + (Math.round(args[2]) & 0xFF); + /* eslint-enable no-bitwise */ const string = integer.toString(16).toUpperCase(); - return '000000'.substring(string.length) + string; + return '000000'.slice(string.length) + string; }; convert.hex.rgb = function (args) { - const match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i); + const match = args.toString(16).match(/[a-f\d]{6}|[a-f\d]{3}/i); if (!match) { return [0, 0, 0]; } @@ -636,15 +691,15 @@ convert.hex.rgb = function (args) { let colorString = match[0]; if (match[0].length === 3) { - colorString = colorString.split('').map(char => { - return char + char; - }).join(''); + colorString = [...colorString].map(char => char + char).join(''); } - const integer = parseInt(colorString, 16); + const integer = Number.parseInt(colorString, 16); + /* eslint-disable no-bitwise */ const r = (integer >> 16) & 0xFF; const g = (integer >> 8) & 0xFF; const b = integer & 0xFF; + /* eslint-enable no-bitwise */ return [r, g, b]; }; @@ -656,22 +711,15 @@ convert.rgb.hcg = function (rgb) { const max = Math.max(Math.max(r, g), b); const min = Math.min(Math.min(r, g), b); const chroma = (max - min); - let grayscale; let hue; - if (chroma < 1) { - grayscale = min / (1 - chroma); - } else { - grayscale = 0; - } + const grayscale = chroma < 1 ? min / (1 - chroma) : 0; if (chroma <= 0) { hue = 0; - } else - if (max === r) { + } else if (max === r) { hue = ((g - b) / chroma) % 6; - } else - if (max === g) { + } else if (max === g) { hue = 2 + (b - r) / chroma; } else { hue = 4 + (r - g) / chroma; @@ -687,11 +735,11 @@ convert.hsl.hcg = function (hsl) { const s = hsl[1] / 100; const l = hsl[2] / 100; - const c = l < 0.5 ? (2.0 * s * l) : (2.0 * s * (1.0 - l)); + const c = l < 0.5 ? (2 * s * l) : (2 * s * (1 - l)); let f = 0; - if (c < 1.0) { - f = (l - 0.5 * c) / (1.0 - c); + if (c < 1) { + f = (l - 0.5 * c) / (1 - c); } return [hsl[0], c * 100, f * 100]; @@ -704,7 +752,7 @@ convert.hsv.hcg = function (hsv) { const c = s * v; let f = 0; - if (c < 1.0) { + if (c < 1) { f = (v - c) / (1 - c); } @@ -716,7 +764,7 @@ convert.hcg.rgb = function (hcg) { const c = hcg[1] / 100; const g = hcg[2] / 100; - if (c === 0.0) { + if (c === 0) { return [g * 255, g * 255, g * 255]; } @@ -728,27 +776,38 @@ convert.hcg.rgb = function (hcg) { /* eslint-disable max-statements-per-line */ switch (Math.floor(hi)) { - case 0: + case 0: { pure[0] = 1; pure[1] = v; pure[2] = 0; break; - case 1: + } + + case 1: { pure[0] = w; pure[1] = 1; pure[2] = 0; break; - case 2: + } + + case 2: { pure[0] = 0; pure[1] = 1; pure[2] = v; break; - case 3: + } + + case 3: { pure[0] = 0; pure[1] = w; pure[2] = 1; break; - case 4: + } + + case 4: { pure[0] = v; pure[1] = 0; pure[2] = 1; break; - default: + } + + default: { pure[0] = 1; pure[1] = 0; pure[2] = w; + } } /* eslint-enable max-statements-per-line */ - mg = (1.0 - c) * g; + mg = (1 - c) * g; return [ (c * pure[0] + mg) * 255, (c * pure[1] + mg) * 255, - (c * pure[2] + mg) * 255 + (c * pure[2] + mg) * 255, ]; }; @@ -756,10 +815,10 @@ convert.hcg.hsv = function (hcg) { const c = hcg[1] / 100; const g = hcg[2] / 100; - const v = c + g * (1.0 - c); + const v = c + g * (1 - c); let f = 0; - if (v > 0.0) { + if (v > 0) { f = c / v; } @@ -770,13 +829,12 @@ convert.hcg.hsl = function (hcg) { const c = hcg[1] / 100; const g = hcg[2] / 100; - const l = g * (1.0 - c) + 0.5 * c; + const l = g * (1 - c) + 0.5 * c; let s = 0; - if (l > 0.0 && l < 0.5) { + if (l > 0 && l < 0.5) { s = c / (2 * l); - } else - if (l >= 0.5 && l < 1.0) { + } else if (l >= 0.5 && l < 1) { s = c / (2 * (1 - l)); } @@ -786,7 +844,7 @@ convert.hcg.hsl = function (hcg) { convert.hcg.hwb = function (hcg) { const c = hcg[1] / 100; const g = hcg[2] / 100; - const v = c + g * (1.0 - c); + const v = c + g * (1 - c); return [hcg[0], (v - c) * 100, (1 - v) * 100]; }; @@ -805,11 +863,11 @@ convert.hwb.hcg = function (hwb) { }; convert.apple.rgb = function (apple) { - return [(apple[0] / 65535) * 255, (apple[1] / 65535) * 255, (apple[2] / 65535) * 255]; + return [(apple[0] / 65_535) * 255, (apple[1] / 65_535) * 255, (apple[2] / 65_535) * 255]; }; convert.rgb.apple = function (rgb) { - return [(rgb[0] / 255) * 65535, (rgb[1] / 255) * 65535, (rgb[2] / 255) * 65535]; + return [(rgb[0] / 255) * 65_535, (rgb[1] / 255) * 65_535, (rgb[2] / 255) * 65_535]; }; convert.gray.rgb = function (args) { @@ -835,14 +893,16 @@ convert.gray.lab = function (gray) { }; convert.gray.hex = function (gray) { - const val = Math.round(gray[0] / 100 * 255) & 0xFF; - const integer = (val << 16) + (val << 8) + val; + /* eslint-disable no-bitwise */ + const value = Math.round(gray[0] / 100 * 255) & 0xFF; + const integer = (value << 16) + (value << 8) + value; + /* eslint-enable no-bitwise */ const string = integer.toString(16).toUpperCase(); - return '000000'.substring(string.length) + string; + return '000000'.slice(string.length) + string; }; convert.rgb.gray = function (rgb) { - const val = (rgb[0] + rgb[1] + rgb[2]) / 3; - return [val / 255 * 100]; + const value = (rgb[0] + rgb[1] + rgb[2]) / 3; + return [value / 255 * 100]; }; diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..d04a625 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,252 @@ +export type Channels = number; +export type RGB = [r: number, g: number, b: number]; +export type HSL = [h: number, s: number, l: number]; +export type HSV = [h: number, s: number, v: number]; +export type CMYK = [c: number, m: number, y: number, k: number]; +export type LAB = [l: number, a: number, b: number]; +export type LCH = [l: number, c: number, h: number]; +export type HCG = [h: number, c: number, g: number]; +export type HWB = [h: number, w: number, b: number]; +export type XYZ = [x: number, y: number, z: number]; +export type Apple = [r16: number, g16: number, b16: number]; +export type Gray = [gray: number]; +export type ANSI16 = number; +export type ANSI256 = number; +export type Keyword = string; +export type HEX = string; + +export type Convert = { + rgb: { + channels: Channels; + labels: 'rgb'; + hsl: { + (...rgb: RGB): HSL; + raw: (...rgb: RGB) => HSL; + }; + hsv: { + (...rgb: RGB): HSV; + raw: (...rgb: RGB) => HSV; + }; + hwb: { + (...rgb: RGB): HWB; + raw: (...rgb: RGB) => HWB; + }; + hcg: { + (...rgb: RGB): HCG; + raw: (...rgb: RGB) => HCG; + }; + cmyk: { + (...rgb: RGB): CMYK; + raw: (...rgb: RGB) => CMYK; + }; + keyword: { + (...rgb: RGB): Keyword; + raw: (...rgb: RGB) => Keyword; + }; + ansi16: { + (...rgb: RGB): ANSI16; + raw: (...rgb: RGB) => ANSI16; + }; + ansi256: { + (...rgb: RGB): ANSI256; + raw: (...rgb: RGB) => ANSI256; + }; + apple: { + (...rgb: RGB): Apple; + raw: (...rgb: RGB) => Apple; + }; + hex: { + (...rgb: RGB): HEX; + raw: (...rgb: RGB) => HEX; + }; + gray: { + (...rgb: RGB): Gray; + raw: (...rgb: RGB) => Gray; + }; + }; + keyword: { + rgb: { + (keyword: Keyword): RGB; + raw: (keyword: Keyword) => RGB; + }; + }; + hsl: { + channels: Channels; + labels: 'hsl'; + rgb: { + (...hsl: HSL): RGB; + raw: (...hsl: HSL) => RGB; + }; + hsv: { + (...hsl: HSL): HSV; + raw: (...hsl: HSL) => HSV; + }; + hcg: { + (...hsl: HSL): HCG; + raw: (...hsl: HSL) => HCG; + }; + }; + hsv: { + channels: Channels; + labels: 'hsv'; + hcg: { + (...hsv: HSV): HCG; + raw: (...hsv: HSV) => HCG; + }; + rgb: { + (...hsv: HSV): RGB; + raw: (...hsv: HSV) => RGB; + }; + hsv: { + (...hsv: HSV): HSV; + raw: (...hsv: HSV) => HSV; + }; + hsl: { + (...hsv: HSV): HSL; + raw: (...hsv: HSV) => HSL; + }; + hwb: { + (...hsv: HSV): HWB; + raw: (...hsv: HSV) => HWB; + }; + ansi16: { + (...hsv: HSV): ANSI16; + raw: (...hsv: HSV) => ANSI16; + }; + }; + hwb: { + channels: Channels; + labels: 'hwb'; + hcg: { + (...hwb: HWB): HCG; + raw: (...hwb: HWB) => HCG; + }; + rgb: { + (...hwb: HWB): RGB; + raw: (...hwb: HWB) => RGB; + }; + }; + cmyk: { + channels: Channels; + labels: 'cmyk'; + rgb: { + (...cmyk: CMYK): RGB; + raw: (...cmyk: CMYK) => RGB; + }; + }; + xyz: { + channels: Channels; + labels: 'xyz'; + rgb: { + (...xyz: XYZ): RGB; + raw: (...xyz: XYZ) => RGB; + }; + lab: { + (...xyz: XYZ): LAB; + raw: (...xyz: XYZ) => LAB; + }; + }; + lab: { + channels: Channels; + labels: 'lab'; + xyz: { + (...lab: LAB): XYZ; + raw: (...lab: LAB) => XYZ; + }; + lch: { + (...lab: LAB): LCH; + raw: (...lab: LAB) => LCH; + }; + }; + lch: { + channels: Channels; + labels: 'lch'; + lab: { + (...lch: LCH): LAB; + raw: (...lch: LCH) => LAB; + }; + }; + hex: { + channels: Channels; + labels: ['hex']; + rgb: { + (hex: HEX): RGB; + raw: (hex: HEX) => RGB; + }; + }; + ansi16: { + channels: Channels; + labels: ['ansi16']; + rgb: { + (ansi16: ANSI16): RGB; + raw: (ansi16: ANSI16) => RGB; + }; + }; + ansi256: { + channels: Channels; + labels: ['ansi256']; + rgb: { + (ansi256: ANSI256): RGB; + raw: (ansi256: ANSI256) => RGB; + }; + }; + hcg: { + channels: Channels; + labels: ['h', 'c', 'g']; + rgb: { + (...hcg: HCG): RGB; + raw: (...hcg: HCG) => RGB; + }; + hsv: { + (...hcg: HCG): HSV; + raw: (...hcg: HCG) => HSV; + }; + hwb: { + (...hcg: HCG): HWB; + raw: (...hcg: HCG) => HWB; + }; + }; + apple: { + channels: Channels; + labels: ['r16', 'g16', 'b16']; + rgb: { + (...apple: Apple): RGB; + raw: (...apple: Apple) => RGB; + }; + }; + gray: { + channels: Channels; + labels: ['gray']; + rgb: { + (...gray: Gray): RGB; + raw: (...gray: Gray) => RGB; + }; + hsl: { + (...gray: Gray): HSL; + raw: (...gray: Gray) => HSL; + }; + hsv: { + (...gray: Gray): HSV; + raw: (...gray: Gray) => HSV; + }; + hwb: { + (...gray: Gray): HWB; + raw: (...gray: Gray) => HWB; + }; + cmyk: { + (...gray: Gray): CMYK; + raw: (...gray: Gray) => CMYK; + }; + lab: { + (...gray: Gray): LAB; + raw: (...gray: Gray) => LAB; + }; + hex: { + (...gray: Gray): HEX; + raw: (...gray: Gray) => HEX; + }; + }; +}; + +declare const convert: Convert; +export default convert; diff --git a/index.js b/index.js index b648e57..c2e4964 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,5 @@ -const conversions = require('./conversions'); -const route = require('./route'); +import conversions from './conversions.js'; +import route from './route.js'; const convert = {}; @@ -45,7 +45,7 @@ function wrapRounded(fn) { // see notice in conversions.js; don't use box types // in conversion functions. if (typeof result === 'object') { - for (let len = result.length, i = 0; i < len; i++) { + for (let {length} = result, i = 0; i < length; i++) { result[i] = Math.round(result[i]); } } @@ -61,7 +61,7 @@ function wrapRounded(fn) { return wrappedFn; } -models.forEach(fromModel => { +for (const fromModel of models) { convert[fromModel] = {}; Object.defineProperty(convert[fromModel], 'channels', {value: conversions[fromModel].channels}); @@ -70,12 +70,12 @@ models.forEach(fromModel => { const routes = route(fromModel); const routeModels = Object.keys(routes); - routeModels.forEach(toModel => { + for (const toModel of routeModels) { const fn = routes[toModel]; convert[fromModel][toModel] = wrapRounded(fn); convert[fromModel][toModel].raw = wrapRaw(fn); - }); -}); + } +} -module.exports = convert; +export default convert; diff --git a/index.test-d.ts b/index.test-d.ts new file mode 100644 index 0000000..b7492e1 --- /dev/null +++ b/index.test-d.ts @@ -0,0 +1,114 @@ +import {expectType} from 'tsd'; +import convert, {type Channels, type RGB, type HSL, type HSV, type CMYK, type LAB, type LCH, type HCG, type HWB, type XYZ, type Apple, type Gray, type ANSI16, type ANSI256, type Keyword, type HEX} from './index.js'; + +// RGB +expectType(convert.rgb.channels); +expectType(convert.rgb.hsl(0, 0, 0)); +expectType(convert.rgb.hsl.raw(0, 0, 0)); +expectType(convert.rgb.hsv(0, 0, 0)); +expectType(convert.rgb.hsv.raw(0, 0, 0)); +expectType(convert.rgb.hwb(0, 0, 0)); +expectType(convert.rgb.hwb.raw(0, 0, 0)); +expectType(convert.rgb.hcg(0, 0, 0)); +expectType(convert.rgb.hcg.raw(0, 0, 0)); +expectType(convert.rgb.cmyk(0, 0, 0)); +expectType(convert.rgb.cmyk.raw(0, 0, 0)); +expectType(convert.rgb.keyword(0, 0, 0)); +expectType(convert.rgb.keyword.raw(0, 0, 0)); +expectType(convert.rgb.ansi16(0, 0, 0)); +expectType(convert.rgb.ansi16.raw(0, 0, 0)); +expectType(convert.rgb.ansi256(0, 0, 0)); +expectType(convert.rgb.ansi256.raw(0, 0, 0)); +expectType(convert.rgb.apple(0, 0, 0)); +expectType(convert.rgb.apple.raw(0, 0, 0)); +expectType(convert.rgb.hex(0, 0, 0)); +expectType(convert.rgb.hex.raw(0, 0, 0)); +expectType(convert.rgb.gray(0, 0, 0)); +expectType(convert.rgb.gray.raw(0, 0, 0)); + +// Keyword +expectType(convert.keyword.rgb('blue')); +expectType(convert.keyword.rgb.raw('blue')); + +// HSL +expectType(convert.hsl.channels); +expectType(convert.hsl.rgb(0, 0, 0)); +expectType(convert.hsl.rgb.raw(0, 0, 0)); +expectType(convert.hsl.hsv(0, 0, 0)); +expectType(convert.hsl.hsv.raw(0, 0, 0)); +expectType(convert.hsl.hcg(0, 0, 0)); +expectType(convert.hsl.hcg.raw(0, 0, 0)); + +// HSV +expectType(convert.hsv.channels); +expectType(convert.hsv.rgb(0, 0, 0)); +expectType(convert.hsv.rgb.raw(0, 0, 0)); +expectType(convert.hsv.hsl(0, 0, 0)); +expectType(convert.hsv.hsl.raw(0, 0, 0)); +expectType(convert.hsv.hcg(0, 0, 0)); +expectType(convert.hsv.hcg.raw(0, 0, 0)); +expectType(convert.hsv.hwb(0, 0, 0)); +expectType(convert.hsv.hwb.raw(0, 0, 0)); +expectType(convert.hsv.ansi16(0, 0, 0)); + +// HWB +expectType(convert.hwb.channels); +expectType(convert.hwb.rgb(0, 0, 0)); +expectType(convert.hwb.rgb.raw(0, 0, 0)); +expectType(convert.hwb.hcg(0, 0, 0)); +expectType(convert.hwb.hcg.raw(0, 0, 0)); + +// CMYK +expectType(convert.cmyk.channels); +expectType(convert.cmyk.rgb(0, 0, 0, 0)); +expectType(convert.cmyk.rgb.raw(0, 0, 0, 0)); + +// XYZ +expectType(convert.xyz.channels); +expectType(convert.xyz.rgb(0, 0, 0)); +expectType(convert.xyz.rgb.raw(0, 0, 0)); +expectType(convert.xyz.lab(0, 0, 0)); +expectType(convert.xyz.lab.raw(0, 0, 0)); + +// LAB +expectType(convert.lab.channels); +expectType(convert.lab.xyz(0, 0, 0)); +expectType(convert.lab.xyz.raw(0, 0, 0)); +expectType(convert.lab.lch(0, 0, 0)); +expectType(convert.lab.lch.raw(0, 0, 0)); + +// LCH +expectType(convert.lch.channels); +expectType(convert.lch.lab(0, 0, 0)); +expectType(convert.lch.lab.raw(0, 0, 0)); + +// HCG +expectType(convert.hcg.channels); +expectType(convert.hcg.rgb(0, 0, 0)); +expectType(convert.hcg.rgb.raw(0, 0, 0)); +expectType(convert.hcg.hsv(0, 0, 0)); +expectType(convert.hcg.hsv.raw(0, 0, 0)); +expectType(convert.hcg.hwb(0, 0, 0)); +expectType(convert.hcg.hwb.raw(0, 0, 0)); + +// Apple +expectType(convert.apple.channels); +expectType(convert.apple.rgb(0, 0, 0)); +expectType(convert.apple.rgb.raw(0, 0, 0)); + +// Gray +expectType(convert.gray.channels); +expectType(convert.gray.rgb(0)); +expectType(convert.gray.rgb.raw(0)); +expectType(convert.gray.hsl(0)); +expectType(convert.gray.hsl.raw(0)); +expectType(convert.gray.hsv(0)); +expectType(convert.gray.hsv.raw(0)); +expectType(convert.gray.hwb(0)); +expectType(convert.gray.hwb.raw(0)); +expectType(convert.gray.cmyk(0)); +expectType(convert.gray.cmyk.raw(0)); +expectType(convert.gray.lab(0)); +expectType(convert.gray.lab.raw(0)); +expectType(convert.gray.hex(0)); +expectType(convert.gray.hex.raw(0)); diff --git a/package.json b/package.json index 5c1a8bf..b835d08 100644 --- a/package.json +++ b/package.json @@ -8,13 +8,21 @@ ], "license": "MIT", "repository": "Qix-/color-convert", - "scripts": { - "pretest": "xo", - "test": "node test/basic.js" - }, + "type": "module", + "exports": "./index.js", + "types": "./index.d.ts", "engines": { - "node": ">=7.0.0" + "node": ">=14.6" + }, + "scripts": { + "test": "xo && tsd && node test/basic.js" }, + "files": [ + "index.js", + "index.d.ts", + "conversions.js", + "route.js" + ], "keywords": [ "color", "colour", @@ -29,25 +37,22 @@ "ansi", "ansi16" ], - "files": [ - "index.js", - "conversions.js", - "route.js" - ], "xo": { "rules": { "default-case": 0, "no-inline-comments": 0, "operator-linebreak": 0, - "unicorn/prefer-exponentiation-operator": 0 + "unicorn/prefer-exponentiation-operator": 0, + "@typescript-eslint/naming-convention": 0 } }, "devDependencies": { - "chalk": "^2.4.2", - "jimp": "^0.16.1", - "xo": "^0.24.0" + "chalk": "^5.2.0", + "jimp": "^0.22.8", + "tsd": "^0.28.1", + "xo": "^0.54.2" }, "dependencies": { - "color-name": "~1.1.4" + "color-name": "^2.0.0" } } diff --git a/route.js b/route.js index 1a08521..1fba609 100644 --- a/route.js +++ b/route.js @@ -1,4 +1,4 @@ -const conversions = require('./conversions'); +import conversions from './conversions.js'; /* This function routes a model to all other models. @@ -16,12 +16,12 @@ function buildGraph() { // https://jsperf.com/object-keys-vs-for-in-with-closure/3 const models = Object.keys(conversions); - for (let len = models.length, i = 0; i < len; i++) { + for (let {length} = models, i = 0; i < length; i++) { graph[models[i]] = { // http://jsperf.com/1-vs-infinity // micro-opt, but this is simple. distance: -1, - parent: null + parent: null, }; } @@ -35,11 +35,11 @@ function deriveBFS(fromModel) { graph[fromModel].distance = 0; - while (queue.length) { + while (queue.length > 0) { const current = queue.pop(); const adjacents = Object.keys(conversions[current]); - for (let len = adjacents.length, i = 0; i < len; i++) { + for (let {length} = adjacents, i = 0; i < length; i++) { const adjacent = adjacents[i]; const node = graph[adjacent]; @@ -75,12 +75,12 @@ function wrapConversion(toModel, graph) { return fn; } -module.exports = function (fromModel) { +function route(fromModel) { const graph = deriveBFS(fromModel); const conversion = {}; const models = Object.keys(graph); - for (let len = models.length, i = 0; i < len; i++) { + for (let {length} = models, i = 0; i < length; i++) { const toModel = models[i]; const node = graph[toModel]; @@ -93,5 +93,6 @@ module.exports = function (fromModel) { } return conversion; -}; +} +export default route; diff --git a/test/ansi-color-grid.js b/test/ansi-color-grid.js index 03717c5..438bbe4 100644 --- a/test/ansi-color-grid.js +++ b/test/ansi-color-grid.js @@ -1,4 +1,5 @@ -const cc = require('..'); +import process from 'node:process'; +import cc from '../index.js'; process.stdout.write('\n'); @@ -11,7 +12,7 @@ for (let i = 0; i < 256; i++) { process.stdout.write( ` [${ - ' '.substr(Math.max(0, Math.floor(Math.log10(i)))) + ' '.slice(Math.max(0, Math.floor(Math.log10(i)))) }${ i } \u001B[48;5;${i}m \u001B[0;${ @@ -19,8 +20,8 @@ for (let i = 0; i < 256; i++) { }m \u001B[m ${ code16 }${ - ' '.substr(Math.max(0, Math.floor(Math.log10(code16)))) - }]` + ' '.slice(Math.max(0, Math.floor(Math.log10(code16)))) + }]`, ); } diff --git a/test/ansi-colorize.js b/test/ansi-colorize.js index 5f2d1e7..d05b5e4 100755 --- a/test/ansi-colorize.js +++ b/test/ansi-colorize.js @@ -1,7 +1,7 @@ #!/usr/bin/env node - -const jimp = require('jimp'); -const cc = require('..'); +import process from 'node:process'; +import jimp from 'jimp'; +import cc from '../index.js'; async function main() { if (process.argv.length !== 4) { @@ -27,13 +27,13 @@ async function main() { img.bitmap.data[idx] = r; img.bitmap.data[idx + 1] = g; img.bitmap.data[idx + 2] = b; - } + }, ); await img.write(outputPath); } -main().catch(error => { +await main().catch(error => { console.error(error); process.exit(1); }); diff --git a/test/basic.js b/test/basic.js index 50bc652..7ccb8c3 100644 --- a/test/basic.js +++ b/test/basic.js @@ -1,15 +1,13 @@ -const assert = require('assert'); -const chalk = require('chalk'); -const keywords = require('color-name'); - -const conversions = require('../conversions'); - -const convert = require('..'); +import assert from 'node:assert'; +import chalk from 'chalk'; +import keywords from 'color-name'; +import conversions from '../conversions.js'; +import convert from '../index.js'; const models = Object.keys(conversions); -for (let len = models.length, i = 0; i < len; i++) { +for (let {length} = models, i = 0; i < length; i++) { const toModel = models[i]; - for (let j = 0; j < len; j++) { + for (let j = 0; j < length; j++) { const fromModel = models[j]; if (toModel === fromModel) { @@ -18,7 +16,7 @@ for (let len = models.length, i = 0; i < len; i++) { const fn = convert[toModel][fromModel]; if (fn) { - const path = (fn.conversion || [fromModel, toModel]).slice(); + const path = [...(fn.conversion || [fromModel, toModel])]; path[0] = chalk.bold.cyan(path[0]); path[path.length - 1] = chalk.bold.cyan(path[path.length - 1]); @@ -30,19 +28,19 @@ for (let len = models.length, i = 0; i < len; i++) { // Should not expose channels assert.ok(convert[toModel].channels > 0); - assert.ok(Object.keys(convert[toModel]).indexOf('channels') === -1); + assert.ok(!Object.keys(convert[toModel]).includes('channels')); } // Labels should be unique const uniqued = {}; -models.forEach(model => { - const hash = [].slice.call(convert[model].labels).sort().join(''); +for (const model of models) { + const hash = Array.prototype.slice.call(convert[model].labels).sort().join(''); if (hash in uniqued) { throw new Error('models ' + uniqued[hash] + ' and ' + model + ' have the same label set'); } uniqued[hash] = model; -}); +} assert.deepStrictEqual(convert.rgb.hsl([140, 200, 100]), [96, 48, 59]); assert.deepStrictEqual(convert.rgb.hsv([140, 200, 100]), [96, 50, 78]); @@ -57,7 +55,7 @@ assert.deepStrictEqual(convert.rgb.ansi16([92, 191, 84]), 32); assert.deepStrictEqual(convert.rgb.ansi256([92, 191, 84]), 114); assert.deepStrictEqual(convert.rgb.hex([92, 191, 84]), '5CBF54'); assert.deepStrictEqual(convert.rgb.hcg([140, 200, 100]), [96, 39, 65]); -assert.deepStrictEqual(convert.rgb.apple([255, 127, 0]), [65535, 32639, 0]); +assert.deepStrictEqual(convert.rgb.apple([255, 127, 0]), [65_535, 32_639, 0]); assert.deepStrictEqual(convert.hsl.rgb([96, 48, 59]), [140, 201, 100]); assert.deepStrictEqual(convert.hsl.hsv([96, 48, 59]), [96, 50, 79]); // Colorpicker says [96,50,79] @@ -125,7 +123,7 @@ assert.deepStrictEqual(convert.hcg.hsv([96, 39, 64]), [96, 50, 78]); assert.deepStrictEqual(convert.hcg.hsl([96, 39, 64]), [96, 47, 59]); // https://github.com/Qix-/color-convert/issues/73 -assert.deepStrictEqual(convert.rgb.hcg.raw([250, 0, 255]), [298.8235294117647, 100, 0]); +assert.deepStrictEqual(convert.rgb.hcg.raw([250, 0, 255]), [298.823_529_411_764_7, 100, 0]); // Non-array arguments assert.deepStrictEqual(convert.hsl.rgb(96, 48, 59), [140, 201, 100]); @@ -155,7 +153,7 @@ assert.deepStrictEqual(round(convert.keyword.rgb.raw('blue')), [0, 0, 255]); assert.deepStrictEqual(convert.rgb.keyword.raw([255, 228, 196]), 'bisque'); assert.deepStrictEqual(round(convert.hsv.hsl.raw([96, 50, 78])), [96, 47, 58.5]); -assert.deepStrictEqual(round(convert.hsv.hsl.raw([302, 32, 55])), [302, 19.0, 46.2]); +assert.deepStrictEqual(round(convert.hsv.hsl.raw([302, 32, 55])), [302, 19, 46.2]); assert.deepStrictEqual(round(convert.hsv.hsl.raw([267, 19, 89])), [267, 43.5, 80.5]); assert.deepStrictEqual(round(convert.hsv.hsl.raw([267, 91, 95])), [267, 89.6, 51.8]); assert.deepStrictEqual(round(convert.hsv.hsl.raw([267, 91, 12])), [267, 83.5, 6.5]); @@ -198,12 +196,12 @@ assert.deepStrictEqual(convert.hwb.rgb([240, 40, 40]), [102, 102, 153]); assert.deepStrictEqual(convert.hwb.rgb([240, 40, 20]), [102, 102, 204]); // Black should always stay black -const val = [0, 0, 0]; -assert.deepStrictEqual(convert.hsl.hsv(val), val); -assert.deepStrictEqual(convert.hsl.rgb(val), val); -assert.deepStrictEqual(convert.hsl.hwb(val), [0, 0, 100]); -assert.deepStrictEqual(convert.hsl.cmyk(val), [0, 0, 0, 100]); -assert.deepStrictEqual(convert.hsl.hex(val), '000000'); +const value = [0, 0, 0]; +assert.deepStrictEqual(convert.hsl.hsv(value), value); +assert.deepStrictEqual(convert.hsl.rgb(value), value); +assert.deepStrictEqual(convert.hsl.hwb(value), [0, 0, 100]); +assert.deepStrictEqual(convert.hsl.cmyk(value), [0, 0, 0, 100]); +assert.deepStrictEqual(convert.hsl.hex(value), '000000'); // Test keyword rounding assert.deepStrictEqual(convert.rgb.keyword(255, 255, 0), 'yellow');