@sardine/colour 2.1.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import type { LabColour } from "./types";
2
2
  /**
3
- * Mesures the colour difference between two colours in the Lab space
3
+ * Measures the colour difference between two colours in the Lab space
4
4
  *
5
5
  * Math taken from:
6
6
  *
@@ -0,0 +1,9 @@
1
+ import type { WCAG } from "./types";
2
+ /**
3
+ * Calculates the contrast ratio between two colours in hexadecimal format.
4
+ * @param colour1 The first colour in hexadecimal format
5
+ * @param colour2 The second colour in hexadecimal format
6
+ * @param standard The standard to evaluate the contrast ratio against, defaults to WCAG2.1
7
+ * @returns The contrast ratio between the two colours truncated to 3 decimal places
8
+ */
9
+ export declare function getContrastRatioFromHex(colour1: string, colour2: string, standard: WCAG): number;
package/dist/index.cjs CHANGED
@@ -68,42 +68,52 @@ function clamp(value, min, max) {
68
68
  return Math.min(Math.max(value, min), max);
69
69
  }
70
70
  function ciede2000(colour1, colour2) {
71
- const L1 = colour1.L;
72
- const a1 = colour1.a;
73
- const b1 = colour1.b;
74
- const L2 = colour2.L;
75
- const a2 = colour2.a;
76
- const b2 = colour2.b;
77
- const kL = 1;
78
- const kC = 1;
79
- const kH = 1;
80
- const C1 = Math.sqrt(a1 ** 2 + b1 ** 2);
81
- const C2 = Math.sqrt(a2 ** 2 + b2 ** 2);
82
- const ΔL_d = L2 - L1;
83
- const = (C1 + C2) / 2;
84
- const G = 0.5 * (1 - bigSquare());
85
- const a1_d = a1 * (1 + G);
86
- const a2_d = a2 * (1 + G);
87
- const C1_d = Math.sqrt(a1_d ** 2 + b1 ** 2);
88
- const C2_d = Math.sqrt(a2_d ** 2 + b2 ** 2);
89
- const C̅_d = (C1_d + C2_d) / 2;
90
- const ΔC̅_d = C2_d - C1_d;
91
- const h1_d = hue_d(b1, a1_d);
92
- const h2_d = hue_d(b2, a2_d);
93
- const Δh_d = deltaHue_d({ C1, C2, h1_d, h2_d });
94
- const ΔH_d = 2 * Math.sqrt(C1_d * C2_d) * Math.sin(toRadians(Δh_d) / 2);
95
- const H̅_d = meanHue_d({ C1, C2, h1_d, h2_d });
96
- const L̅ = (L1 + L2) / 2;
97
- const T = 1 - 0.17 * Math.cos(toRadians(H̅_d - 30)) + 0.24 * Math.cos(toRadians(2 * H̅_d)) + 0.32 * Math.cos(toRadians(3 * H̅_d + 6)) - 0.2 * Math.cos(toRadians(4 * H̅_d - 63));
98
- const SL = 1 + 0.015 * (L̅ - 50) ** 2 / Math.sqrt(20 + (L̅ - 50) ** 2);
99
- const SC = 0.045 * C̅_d + 1;
100
- const SH = 1 + 0.015 * C̅_d * T;
101
- const rotation = 30 * Math.exp(-(((H̅_d - 275) / 25) ** 2));
102
- const RT = -2 * bigSquare(C̅_d) * Math.sin(toRadians(rotation * 2));
103
- const ΔE = Math.sqrt(
104
- (ΔL_d / (kL * SL)) ** 2 + (ΔC̅_d / (kC * SC)) ** 2 + (ΔH_d / (kH * SH)) ** 2 + RT * (ΔC̅_d / (kC * SC)) * (ΔH_d / (kH * SH))
71
+ const lightness1 = colour1.L;
72
+ const greenRed1 = colour1.a;
73
+ const blueYellow1 = colour1.b;
74
+ const lightness2 = colour2.L;
75
+ const greenRed2 = colour2.a;
76
+ const blueYellow2 = colour2.b;
77
+ const luminanceWeight = 1;
78
+ const chromaWeight = 1;
79
+ const hueWeight = 1;
80
+ const chroma1 = Math.sqrt(greenRed1 ** 2 + blueYellow1 ** 2);
81
+ const chroma2 = Math.sqrt(greenRed2 ** 2 + blueYellow2 ** 2);
82
+ const deltaLightness = lightness2 - lightness1;
83
+ const meanChroma = (chroma1 + chroma2) / 2;
84
+ const G = 0.5 * (1 - bigSquare(meanChroma));
85
+ const greenRed1Prime = greenRed1 * (1 + G);
86
+ const greenRed2Prime = greenRed2 * (1 + G);
87
+ const chroma1Prime = Math.sqrt(greenRed1Prime ** 2 + blueYellow1 ** 2);
88
+ const chroma2Prime = Math.sqrt(greenRed2Prime ** 2 + blueYellow2 ** 2);
89
+ const meanChromaPrime = (chroma1Prime + chroma2Prime) / 2;
90
+ const deltaChromaPrime = chroma2Prime - chroma1Prime;
91
+ const hue1Prime = hue_d(blueYellow1, greenRed1Prime);
92
+ const hue2Prime = hue_d(blueYellow2, greenRed2Prime);
93
+ const deltaHuePrime = deltaHue_d({
94
+ C1: chroma1,
95
+ C2: chroma2,
96
+ h1_d: hue1Prime,
97
+ h2_d: hue2Prime
98
+ });
99
+ const deltaHue = 2 * Math.sqrt(chroma1Prime * chroma2Prime) * Math.sin(toRadians(deltaHuePrime) / 2);
100
+ const meanHuePrime = meanHue_d({
101
+ C1: chroma1,
102
+ C2: chroma2,
103
+ h1_d: hue1Prime,
104
+ h2_d: hue2Prime
105
+ });
106
+ const meanLightness = (lightness1 + lightness2) / 2;
107
+ const T = 1 - 0.17 * Math.cos(toRadians(meanHuePrime - 30)) + 0.24 * Math.cos(toRadians(2 * meanHuePrime)) + 0.32 * Math.cos(toRadians(3 * meanHuePrime + 6)) - 0.2 * Math.cos(toRadians(4 * meanHuePrime - 63));
108
+ const SL = 1 + 0.015 * (meanLightness - 50) ** 2 / Math.sqrt(20 + (meanLightness - 50) ** 2);
109
+ const SC = 0.045 * meanChromaPrime + 1;
110
+ const SH = 1 + 0.015 * meanChromaPrime * T;
111
+ const rotation = 30 * Math.exp(-(((meanHuePrime - 275) / 25) ** 2));
112
+ const RT = -2 * bigSquare(meanChromaPrime) * Math.sin(toRadians(rotation * 2));
113
+ const deltaE = Math.sqrt(
114
+ (deltaLightness / (luminanceWeight * SL)) ** 2 + (deltaChromaPrime / (chromaWeight * SC)) ** 2 + (deltaHue / (hueWeight * SH)) ** 2 + RT * (deltaChromaPrime / (chromaWeight * SC)) * (deltaHue / (hueWeight * SH))
105
115
  );
106
- return ΔE;
116
+ return deltaE;
107
117
  }
108
118
  function convertRGBtoXYZ(colour) {
109
119
  const { R, G, B } = colour;
@@ -508,6 +518,14 @@ function getSRGBLuminanceFromHex(colour, standard) {
508
518
  const rgbColor = convertHextoRGB(colour);
509
519
  return getSRGBLuminanceFromRGB(rgbColor, standard);
510
520
  }
521
+ function getContrastRatioFromHex(colour1, colour2, standard) {
522
+ const luminance1 = getSRGBLuminanceFromHex(colour1, standard);
523
+ const luminance2 = getSRGBLuminanceFromHex(colour2, standard);
524
+ const lighter = Math.max(luminance1, luminance2);
525
+ const darker = Math.min(luminance1, luminance2);
526
+ const ratio = (lighter + 0.05) / (darker + 0.05);
527
+ return Math.floor(ratio * 1e3) / 1e3;
528
+ }
511
529
  function isHexDarkColour(colour, standard) {
512
530
  const colourLuminance = getSRGBLuminanceFromHex(colour, standard) + 0.05;
513
531
  const whiteContrast = 1.05 / colourLuminance;
@@ -587,6 +605,7 @@ exports.findNearestColour = findNearestColour;
587
605
  exports.findNearestHexColour = findNearestHexColour;
588
606
  exports.findNearestNamedCSSColour = findNearestNamedCSSColour;
589
607
  exports.findNearestRGBColour = findNearestRGBColour;
608
+ exports.getContrastRatioFromHex = getContrastRatioFromHex;
590
609
  exports.getSRGBLuminanceFromHex = getSRGBLuminanceFromHex;
591
610
  exports.getSRGBLuminanceFromRGB = getSRGBLuminanceFromRGB;
592
611
  exports.isCSSNamedDarkColour = isCSSNamedDarkColour;
package/dist/index.d.ts CHANGED
@@ -17,6 +17,7 @@ export { findNearestColour } from "./findNearestColour";
17
17
  export { findNearestHexColour } from "./findNearestHexColour";
18
18
  export { findNearestNamedCSSColour } from "./findNearestNamedCSSColour";
19
19
  export { findNearestRGBColour } from "./findNearestRGBColour";
20
+ export { getContrastRatioFromHex } from "./getContrastRatioFromHex";
20
21
  export { getSRGBLuminanceFromHex } from "./getSRGBLuminanceFromHex";
21
22
  export { getSRGBLuminanceFromRGB } from "./getSRGBLuminanceFromRGB";
22
23
  export { isCSSNamedDarkColour } from "./isCSSNameDarkColour";
package/dist/index.min.js CHANGED
@@ -1 +1 @@
1
- "use strict";function x(e,t){if(e===0&&t===0)return 0;const n=Math.atan2(e,t)*(180/Math.PI);return n>=0?n:n+360}function me({C1:e,C2:t,h1_d:n,h2_d:r}){return e*t===0?0:Math.abs(r-n)<=180?r-n:r-n>180?r-n-360:r-n<-180?r-n+360:0}function ge({C1:e,C2:t,h1_d:n,h2_d:r}){return e*t===0?r+n:Math.abs(n-r)<=180?(r+n)/2:Math.abs(n-r)>180&&n+r<360?(r+n+360)/2:Math.abs(n-r)>180&&n+r>=360?(r+n-360)/2:0}const d=e=>e*(Math.PI/180),z=e=>Math.sqrt(e**7/(e**7+25**7));function b(e,t){const n=e/255,r=t?.03928:.04045;let o;return n>r?o=((n+.055)/1.055)**2.4:o=n/12.92,o}function v(e){const t=.20689655172413793,n=t**3;let r;return e>n?r=Math.cbrt(e):r=e/(3*t**2)+4/29,r}function Ce(e,t,n){return Math.min(Math.max(e,t),n)}function j(e,t){const n=e.L,r=e.a,o=e.b,a=t.L,f=t.a,s=t.b,c=1,l=1,G=1,m=Math.sqrt(r**2+o**2),g=Math.sqrt(f**2+s**2),se=a-n,ce=(m+g)/2,A=.5*(1-z(ce)),E=r*(1+A),F=f*(1+A),k=Math.sqrt(E**2+o**2),B=Math.sqrt(F**2+s**2),$=(k+B)/2,X=B-k,Y=x(o,E),Z=x(s,F),ie=me({C1:m,C2:g,h1_d:Y,h2_d:Z}),D=2*Math.sqrt(k*B)*Math.sin(d(ie)/2),C=ge({C1:m,C2:g,h1_d:Y,h2_d:Z}),W=(n+a)/2,ue=1-.17*Math.cos(d(C-30))+.24*Math.cos(d(2*C))+.32*Math.cos(d(3*C+6))-.2*Math.cos(d(4*C-63)),le=1+.015*(W-50)**2/Math.sqrt(20+(W-50)**2),T=.045*$+1,P=1+.015*$*ue,de=30*Math.exp(-(((C-275)/25)**2)),be=-2*z($)*Math.sin(d(de*2));return Math.sqrt((se/(c*le))**2+(X/(l*T))**2+(D/(G*P))**2+be*(X/(l*T))*(D/(G*P)))}function O(e){const{R:t,G:n,B:r}=e,o=b(t)*100,a=b(n)*100,f=b(r)*100,s=o*.4124+a*.3576+f*.1805,c=o*.2126+a*.7152+f*.0722,l=o*.0193+a*.1192+f*.9505;return{X:s,Y:c,Z:l}}function U(e){const{X:t,Y:n,Z:r}=e,o=t/95.047,a=n/100,f=r/108.883,s=v(o),c=v(a),l=v(f),G=116*c-16,m=500*(s-c),g=200*(c-l);return{L:G,a:m,b:g}}function y(e){const t=O(e);return U(t)}const V=(e,t)=>{const n=y(e),r=y(t);return j(n,r)},J=/^#[a-fA-F0-9]{6}$/,K=/^#[a-fA-F0-9]{8}$/,Q=/^#[a-fA-F0-9]{3}$/,_=/^#[a-fA-F0-9]{4}$/,ee=/^rgba*\(\s*([-+]?\d+)\s*(?:,)?\s*([-+]?\d+)\s*(?:,)?\s*([-+]?\d+)\s*(?:,*|\/*)\s*([-+]?\d*[.]?\d+[%]?)*\)$/i;function i(e){const t=e.match(ee);if(!t)throw new Error(`convertCSSRGBtoHex expects a valid CSS RGB string but got ${e}`);const n=r=>r?Number.parseFloat(r):void 0;return{R:n(t[1]),G:n(t[2]),B:n(t[3]),A:n(t[4])}}function p({R:e,G:t,B:n,A:r}){const o=a=>Ce(a,0,255).toString(16).padStart(2,"0");return`#${o(e)}${o(t)}${o(n)}${r?o(Math.round(r*255)):""}`}function pe(e){const t=i(e);return p(t)}const w=new Map([["aliceblue","#f0f8ff"],["antiquewhite","#faebd7"],["aqua","#00ffff"],["aquamarine","#7fffd4"],["azure","#f0ffff"],["beige","#f5f5dc"],["bisque","#ffe4c4"],["black","#000000"],["blanchedalmond","#ffebcd"],["blue","#0000ff"],["blueviolet","#8a2be2"],["brown","#a52a2a"],["burlywood","#deb887"],["cadetblue","#5f9ea0"],["chartreuse","#7fff00"],["chocolate","#d2691e"],["coral","#ff7f50"],["cornflowerblue","#6495ed"],["cornsilk","#fff8dc"],["crimson","#dc143c"],["cyan","#00ffff"],["darkblue","#00008b"],["darkcyan","#008b8b"],["darkgoldenrod","#b8860b"],["darkgray","#a9a9a9"],["darkgreen","#006400"],["darkgrey","#a9a9a9"],["darkkhaki","#bdb76b"],["darkmagenta","#8b008b"],["darkolivegreen","#556b2f"],["darkorange","#ff8c00"],["darkorchid","#9932cc"],["darkred","#8b0000"],["darksalmon","#e9967a"],["darkseagreen","#8fbc8f"],["darkslateblue","#483d8b"],["darkslategray","#2f4f4f"],["darkslategrey","#2f4f4f"],["darkturquoise","#00ced1"],["darkviolet","#9400d3"],["deeppink","#ff1493"],["deepskyblue","#00bfff"],["dimgray","#696969"],["dimgrey","#696969"],["dodgerblue","#1e90ff"],["firebrick","#b22222"],["floralwhite","#fffaf0"],["forestgreen","#228b22"],["fuchsia","#ff00ff"],["gainsboro","#dcdcdc"],["ghostwhite","#f8f8ff"],["gold","#ffd700"],["goldenrod","#daa520"],["gray","#808080"],["green","#008000"],["greenyellow","#adff2f"],["grey","#808080"],["honeydew","#f0fff0"],["hotpink","#ff69b4"],["indianred","#cd5c5c"],["indigo","#4b0082"],["ivory","#fffff0"],["khaki","#f0e68c"],["lavender","#e6e6fa"],["lavenderblush","#fff0f5"],["lawngreen","#7cfc00"],["lemonchiffon","#fffacd"],["lightblue","#add8e6"],["lightcoral","#f08080"],["lightcyan","#e0ffff"],["lightgoldenrodyellow","#fafad2"],["lightgray","#d3d3d3"],["lightgreen","#90ee90"],["lightgrey","#d3d3d3"],["lightpink","#ffb6c1"],["lightsalmon","#ffa07a"],["lightseagreen","#20b2aa"],["lightskyblue","#87cefa"],["lightslategray","#778899"],["lightslategrey","#778899"],["lightsteelblue","#b0c4de"],["lightyellow","#ffffe0"],["lime","#00ff00"],["limegreen","#32cd32"],["linen","#faf0e6"],["magenta","#ff00ff"],["maroon","#800000"],["mediumaquamarine","#66cdaa"],["mediumblue","#0000cd"],["mediumorchid","#ba55d3"],["mediumpurple","#9370db"],["mediumseagreen","#3cb371"],["mediumslateblue","#7b68ee"],["mediumspringgreen","#00fa9a"],["mediumturquoise","#48d1cc"],["mediumvioletred","#c71585"],["midnightblue","#191970"],["mintcream","#f5fffa"],["mistyrose","#ffe4e1"],["moccasin","#ffe4b5"],["navajowhite","#ffdead"],["navy","#000080"],["oldlace","#fdf5e6"],["olive","#808000"],["olivedrab","#6b8e23"],["orangered","#ff4500"],["orchid","#da70d6"],["palegoldenrod","#eee8aa"],["palegreen","#98fb98"],["paleturquoise","#afeeee"],["palevioletred","#db7093"],["papayawhip","#ffefd5"],["peachpuff","#ffdab9"],["peru","#cd853f"],["pink","#ffc0cb"],["plum","#dda0dd"],["powderblue","#b0e0e6"],["purple","#800080"],["red","#ff0000"],["rosybrown","#bc8f8f"],["royalblue","#4169e1"],["saddlebrown","#8b4513"],["salmon","#fa8072"],["sandybrown","#f4a460"],["seagreen","#2e8b57"],["seashell","#fff5ee"],["sienna","#a0522d"],["silver","#c0c0c0"],["skyblue","#87ceeb"],["slateblue","#6a5acd"],["slategray","#708090"],["slategrey","#708090"],["snow","#fffafa"],["springgreen","#00ff7f"],["steelblue","#4682b4"],["tan","#d2b48c"],["teal","#008080"],["thistle","#d8bfd8"],["tomato","#ff6347"],["transparent","#00000000"],["turquoise","#40e0d0"],["violet","#ee82ee"],["wheat","#f5deb3"],["white","#ffffff"],["whitesmoke","#f5f5f5"],["yellow","#ffff00"],["yellowgreen","#9acd32"]]);function te(e){for(const[t,n]of w.entries())if(n===e)return t}function u(e){if(typeof e!="string")throw new Error(`convertHextoRGB expects a string but got a ${typeof e}`);if(e.match(J))return{R:Number.parseInt(`${e[1]}${e[2]}`,16),G:Number.parseInt(`${e[3]}${e[4]}`,16),B:Number.parseInt(`${e[5]}${e[6]}`,16)};if(e.match(Q))return{R:Number.parseInt(`${e[1]}${e[1]}`,16),G:Number.parseInt(`${e[2]}${e[2]}`,16),B:Number.parseInt(`${e[3]}${e[3]}`,16)};if(e.match(K))return{R:Number.parseInt(`${e[1]}${e[2]}`,16),G:Number.parseInt(`${e[3]}${e[4]}`,16),B:Number.parseInt(`${e[5]}${e[6]}`,16),A:Number.parseInt(`${e[7]}${e[8]}`,16)/255};if(e.match(_))return{R:Number.parseInt(`${e[1]}${e[1]}`,16),G:Number.parseInt(`${e[2]}${e[2]}`,16),B:Number.parseInt(`${e[3]}${e[3]}`,16),A:Number.parseInt(`${e[4]}${e[4]}`,16)/255};throw new Error(`convertHextoRGB expects an valid hexadecimal colour value but got ${e}`)}function N(e){return w.get(e)}function h(e){const t=N(e);if(t)return u(t)}function M({R:e,G:t,B:n,A:r}){return`rgb(${e} ${t} ${n}${r?` / ${r}`:""})`}function H(e){const t=p(e);return te(t)}function R(e,t){if(!t||t.length<2)return e;const n=[];for(const o of t){const a=V(e,o);n.push([o,a])}return n.sort((o,a)=>o[1]-a[1])[0][0]}function he(e,t){if(!t||t.length<2)return e;const n=i(e),r=t.map(a=>i(a)),o=R(n,r);return M(o)}function L(e){return!!e.match(ee)}function ne(e){return!!e.match(J)||!!e.match(K)||!!e.match(_)||!!e.match(Q)}function re(e){return w.has(e)}function Re(e,t){if(!t||t.length<2)return e;let n,r;const o=[];if(ne(e)&&(n=u(e),r="hex"),L(e)&&(n=i(e),r="cssRGB"),re(e)&&(n=h(e),r="namedCSS"),!n)return;for(const f of t)ne(f)&&o.push(u(f)),L(f)&&o.push(i(f)),re(f)&&o.push(h(f));if(o.length<2)return e;const a=R(n,o);if(r==="hex")return p(a);if(r==="cssRGB")return M(a);if(r==="namedCSS")return H(a)}function Se(e,t){if(!t||t.length<2)return e;const n=u(e),r=t.map(a=>u(a)),o=R(n,r);return p(o)}function Ge(e,t){if(!t||t.length<2)return e;const n=h(e),o=t.map(f=>h(f)).filter(f=>f!==void 0);if(!n||o.length<2)return e;const a=R(n,o);return H(a)}function q({R:e,G:t,B:n},r){const o=r==="WCAG2.1",a=b(e,o),f=b(t,o),s=b(n,o);return .2126*a+.7152*f+.0722*s}function S(e,t){const n=u(e);return q(n,t)}function I(e,t){const n=S(e,t)+.05,r=1.05/n,o=n/.05;return r>o}function oe(e,t){const n=N(e);if(n)return I(n,t);throw new Error(`${e} is not a valid colour format. isCSSNamedDarkColour only accepts CSS named colours. Check more details here https://developer.mozilla.org/en-US/docs/Web/CSS/named-color`)}function ae(e,t){const n=i(e),r=q(n,t),o=1.05/r,a=r/.05;return o>a}function ke(e,t){try{return e.startsWith("#")?I(e,t):e.startsWith("rgb")?ae(e,t):oe(e,t)}catch{throw new Error(`${e} is not a valid colour format. isDarkColour accepts CSS RGB formats, ie rgb(0,0,0) and rgba(255, 255, 255, 0.4), hexadecimal and CSS named colours.`)}}const fe=(e,t)=>e>t?e/t:t/e,Be=({backgroundColour:e,optionOneColour:t,optionTwoColour:n},r)=>{const o=S(e,r)+.05,a=S(t,r)+.05,f=S(n,r)+.05,s=fe(a,o),c=fe(f,o);return s>c?t:n};export{V as RGBdistance,j as ciede2000,pe as convertCSSRGBtoHex,i as convertCSSRGBtoRGB,te as convertHextoNamedCSSColour,u as convertHextoRGB,N as convertNamedCSSColourtoHex,h as convertNamedCSSColourtoRGB,M as convertRGBtoCSSRGB,p as convertRGBtoHex,y as convertRGBtoLab,H as convertRGBtoNamedCSSColour,O as convertRGBtoXYZ,U as convertXYZtoLab,he as findNearestCSSRGBColour,Re as findNearestColour,Se as findNearestHexColour,Ge as findNearestNamedCSSColour,R as findNearestRGBColour,S as getSRGBLuminanceFromHex,q as getSRGBLuminanceFromRGB,oe as isCSSNamedDarkColour,L as isCSSRGBColour,ae as isCSSRGBDarkColour,ke as isDarkColour,I as isHexDarkColour,Be as pickHexColourContrast};
1
+ "use strict";function T(e,t){if(e===0&&t===0)return 0;const n=Math.atan2(e,t)*(180/Math.PI);return n>=0?n:n+360}function be({C1:e,C2:t,h1_d:n,h2_d:r}){return e*t===0?0:Math.abs(r-n)<=180?r-n:r-n>180?r-n-360:r-n<-180?r-n+360:0}function ge({C1:e,C2:t,h1_d:n,h2_d:r}){return e*t===0?r+n:Math.abs(n-r)<=180?(r+n)/2:Math.abs(n-r)>180&&n+r<360?(r+n+360)/2:Math.abs(n-r)>180&&n+r>=360?(r+n-360)/2:0}const m=e=>e*(Math.PI/180),z=e=>Math.sqrt(e**7/(e**7+25**7));function b(e,t){const n=e/255,r=t?.03928:.04045;let o;return n>r?o=((n+.055)/1.055)**2.4:o=n/12.92,o}function v(e){const t=.20689655172413793,n=t**3;let r;return e>n?r=Math.cbrt(e):r=e/(3*t**2)+4/29,r}function he(e,t,n){return Math.min(Math.max(e,t),n)}function j(e,t){const n=e.L,r=e.a,o=e.b,a=t.L,s=t.a,f=t.b,c=1,d=1,G=1,g=Math.sqrt(r**2+o**2),h=Math.sqrt(s**2+f**2),fe=a-n,ce=(g+h)/2,P=.5*(1-z(ce)),A=r*(1+P),Y=s*(1+P),B=Math.sqrt(A**2+o**2),$=Math.sqrt(Y**2+f**2),k=(B+$)/2,F=$-B,W=T(o,A),E=T(f,Y),ie=be({C1:g,C2:h,h1_d:W,h2_d:E}),X=2*Math.sqrt(B*$)*Math.sin(m(ie)/2),C=ge({C1:g,C2:h,h1_d:W,h2_d:E}),Z=(n+a)/2,ue=1-.17*Math.cos(m(C-30))+.24*Math.cos(m(2*C))+.32*Math.cos(m(3*C+6))-.2*Math.cos(m(4*C-63)),le=1+.015*(Z-50)**2/Math.sqrt(20+(Z-50)**2),D=.045*k+1,x=1+.015*k*ue,de=30*Math.exp(-(((C-275)/25)**2)),me=-2*z(k)*Math.sin(m(de*2));return Math.sqrt((fe/(c*le))**2+(F/(d*D))**2+(X/(G*x))**2+me*(F/(d*D))*(X/(G*x)))}function O(e){const{R:t,G:n,B:r}=e,o=b(t)*100,a=b(n)*100,s=b(r)*100,f=o*.4124+a*.3576+s*.1805,c=o*.2126+a*.7152+s*.0722,d=o*.0193+a*.1192+s*.9505;return{X:f,Y:c,Z:d}}function U(e){const{X:t,Y:n,Z:r}=e,o=t/95.047,a=n/100,s=r/108.883,f=v(o),c=v(a),d=v(s),G=116*c-16,g=500*(f-c),h=200*(c-d);return{L:G,a:g,b:h}}function w(e){const t=O(e);return U(t)}const V=(e,t)=>{const n=w(e),r=w(t);return j(n,r)},J=/^#[a-fA-F0-9]{6}$/,K=/^#[a-fA-F0-9]{8}$/,Q=/^#[a-fA-F0-9]{3}$/,_=/^#[a-fA-F0-9]{4}$/,ee=/^rgba*\(\s*([-+]?\d+)\s*(?:,)?\s*([-+]?\d+)\s*(?:,)?\s*([-+]?\d+)\s*(?:,*|\/*)\s*([-+]?\d*[.]?\d+[%]?)*\)$/i;function i(e){const t=e.match(ee);if(!t)throw new Error(`convertCSSRGBtoHex expects a valid CSS RGB string but got ${e}`);const n=r=>r?Number.parseFloat(r):void 0;return{R:n(t[1]),G:n(t[2]),B:n(t[3]),A:n(t[4])}}function p({R:e,G:t,B:n,A:r}){const o=a=>he(a,0,255).toString(16).padStart(2,"0");return`#${o(e)}${o(t)}${o(n)}${r?o(Math.round(r*255)):""}`}function Ce(e){const t=i(e);return p(t)}const y=new Map([["aliceblue","#f0f8ff"],["antiquewhite","#faebd7"],["aqua","#00ffff"],["aquamarine","#7fffd4"],["azure","#f0ffff"],["beige","#f5f5dc"],["bisque","#ffe4c4"],["black","#000000"],["blanchedalmond","#ffebcd"],["blue","#0000ff"],["blueviolet","#8a2be2"],["brown","#a52a2a"],["burlywood","#deb887"],["cadetblue","#5f9ea0"],["chartreuse","#7fff00"],["chocolate","#d2691e"],["coral","#ff7f50"],["cornflowerblue","#6495ed"],["cornsilk","#fff8dc"],["crimson","#dc143c"],["cyan","#00ffff"],["darkblue","#00008b"],["darkcyan","#008b8b"],["darkgoldenrod","#b8860b"],["darkgray","#a9a9a9"],["darkgreen","#006400"],["darkgrey","#a9a9a9"],["darkkhaki","#bdb76b"],["darkmagenta","#8b008b"],["darkolivegreen","#556b2f"],["darkorange","#ff8c00"],["darkorchid","#9932cc"],["darkred","#8b0000"],["darksalmon","#e9967a"],["darkseagreen","#8fbc8f"],["darkslateblue","#483d8b"],["darkslategray","#2f4f4f"],["darkslategrey","#2f4f4f"],["darkturquoise","#00ced1"],["darkviolet","#9400d3"],["deeppink","#ff1493"],["deepskyblue","#00bfff"],["dimgray","#696969"],["dimgrey","#696969"],["dodgerblue","#1e90ff"],["firebrick","#b22222"],["floralwhite","#fffaf0"],["forestgreen","#228b22"],["fuchsia","#ff00ff"],["gainsboro","#dcdcdc"],["ghostwhite","#f8f8ff"],["gold","#ffd700"],["goldenrod","#daa520"],["gray","#808080"],["green","#008000"],["greenyellow","#adff2f"],["grey","#808080"],["honeydew","#f0fff0"],["hotpink","#ff69b4"],["indianred","#cd5c5c"],["indigo","#4b0082"],["ivory","#fffff0"],["khaki","#f0e68c"],["lavender","#e6e6fa"],["lavenderblush","#fff0f5"],["lawngreen","#7cfc00"],["lemonchiffon","#fffacd"],["lightblue","#add8e6"],["lightcoral","#f08080"],["lightcyan","#e0ffff"],["lightgoldenrodyellow","#fafad2"],["lightgray","#d3d3d3"],["lightgreen","#90ee90"],["lightgrey","#d3d3d3"],["lightpink","#ffb6c1"],["lightsalmon","#ffa07a"],["lightseagreen","#20b2aa"],["lightskyblue","#87cefa"],["lightslategray","#778899"],["lightslategrey","#778899"],["lightsteelblue","#b0c4de"],["lightyellow","#ffffe0"],["lime","#00ff00"],["limegreen","#32cd32"],["linen","#faf0e6"],["magenta","#ff00ff"],["maroon","#800000"],["mediumaquamarine","#66cdaa"],["mediumblue","#0000cd"],["mediumorchid","#ba55d3"],["mediumpurple","#9370db"],["mediumseagreen","#3cb371"],["mediumslateblue","#7b68ee"],["mediumspringgreen","#00fa9a"],["mediumturquoise","#48d1cc"],["mediumvioletred","#c71585"],["midnightblue","#191970"],["mintcream","#f5fffa"],["mistyrose","#ffe4e1"],["moccasin","#ffe4b5"],["navajowhite","#ffdead"],["navy","#000080"],["oldlace","#fdf5e6"],["olive","#808000"],["olivedrab","#6b8e23"],["orangered","#ff4500"],["orchid","#da70d6"],["palegoldenrod","#eee8aa"],["palegreen","#98fb98"],["paleturquoise","#afeeee"],["palevioletred","#db7093"],["papayawhip","#ffefd5"],["peachpuff","#ffdab9"],["peru","#cd853f"],["pink","#ffc0cb"],["plum","#dda0dd"],["powderblue","#b0e0e6"],["purple","#800080"],["red","#ff0000"],["rosybrown","#bc8f8f"],["royalblue","#4169e1"],["saddlebrown","#8b4513"],["salmon","#fa8072"],["sandybrown","#f4a460"],["seagreen","#2e8b57"],["seashell","#fff5ee"],["sienna","#a0522d"],["silver","#c0c0c0"],["skyblue","#87ceeb"],["slateblue","#6a5acd"],["slategray","#708090"],["slategrey","#708090"],["snow","#fffafa"],["springgreen","#00ff7f"],["steelblue","#4682b4"],["tan","#d2b48c"],["teal","#008080"],["thistle","#d8bfd8"],["tomato","#ff6347"],["transparent","#00000000"],["turquoise","#40e0d0"],["violet","#ee82ee"],["wheat","#f5deb3"],["white","#ffffff"],["whitesmoke","#f5f5f5"],["yellow","#ffff00"],["yellowgreen","#9acd32"]]);function te(e){for(const[t,n]of y.entries())if(n===e)return t}function u(e){if(typeof e!="string")throw new Error(`convertHextoRGB expects a string but got a ${typeof e}`);if(e.match(J))return{R:Number.parseInt(`${e[1]}${e[2]}`,16),G:Number.parseInt(`${e[3]}${e[4]}`,16),B:Number.parseInt(`${e[5]}${e[6]}`,16)};if(e.match(Q))return{R:Number.parseInt(`${e[1]}${e[1]}`,16),G:Number.parseInt(`${e[2]}${e[2]}`,16),B:Number.parseInt(`${e[3]}${e[3]}`,16)};if(e.match(K))return{R:Number.parseInt(`${e[1]}${e[2]}`,16),G:Number.parseInt(`${e[3]}${e[4]}`,16),B:Number.parseInt(`${e[5]}${e[6]}`,16),A:Number.parseInt(`${e[7]}${e[8]}`,16)/255};if(e.match(_))return{R:Number.parseInt(`${e[1]}${e[1]}`,16),G:Number.parseInt(`${e[2]}${e[2]}`,16),B:Number.parseInt(`${e[3]}${e[3]}`,16),A:Number.parseInt(`${e[4]}${e[4]}`,16)/255};throw new Error(`convertHextoRGB expects an valid hexadecimal colour value but got ${e}`)}function M(e){return y.get(e)}function R(e){const t=M(e);if(t)return u(t)}function N({R:e,G:t,B:n,A:r}){return`rgb(${e} ${t} ${n}${r?` / ${r}`:""})`}function H(e){const t=p(e);return te(t)}function S(e,t){if(!t||t.length<2)return e;const n=[];for(const o of t){const a=V(e,o);n.push([o,a])}return n.sort((o,a)=>o[1]-a[1])[0][0]}function pe(e,t){if(!t||t.length<2)return e;const n=i(e),r=t.map(a=>i(a)),o=S(n,r);return N(o)}function q(e){return!!e.match(ee)}function ne(e){return!!e.match(J)||!!e.match(K)||!!e.match(_)||!!e.match(Q)}function re(e){return y.has(e)}function Re(e,t){if(!t||t.length<2)return e;let n,r;const o=[];if(ne(e)&&(n=u(e),r="hex"),q(e)&&(n=i(e),r="cssRGB"),re(e)&&(n=R(e),r="namedCSS"),!n)return;for(const s of t)ne(s)&&o.push(u(s)),q(s)&&o.push(i(s)),re(s)&&o.push(R(s));if(o.length<2)return e;const a=S(n,o);if(r==="hex")return p(a);if(r==="cssRGB")return N(a);if(r==="namedCSS")return H(a)}function Se(e,t){if(!t||t.length<2)return e;const n=u(e),r=t.map(a=>u(a)),o=S(n,r);return p(o)}function Ge(e,t){if(!t||t.length<2)return e;const n=R(e),o=t.map(s=>R(s)).filter(s=>s!==void 0);if(!n||o.length<2)return e;const a=S(n,o);return H(a)}function I({R:e,G:t,B:n},r){const o=r==="WCAG2.1",a=b(e,o),s=b(t,o),f=b(n,o);return .2126*a+.7152*s+.0722*f}function l(e,t){const n=u(e);return I(n,t)}function Be(e,t,n){const r=l(e,n),o=l(t,n),a=Math.max(r,o),s=Math.min(r,o),f=(a+.05)/(s+.05);return Math.floor(f*1e3)/1e3}function L(e,t){const n=l(e,t)+.05,r=1.05/n,o=n/.05;return r>o}function oe(e,t){const n=M(e);if(n)return L(n,t);throw new Error(`${e} is not a valid colour format. isCSSNamedDarkColour only accepts CSS named colours. Check more details here https://developer.mozilla.org/en-US/docs/Web/CSS/named-color`)}function ae(e,t){const n=i(e),r=I(n,t),o=1.05/r,a=r/.05;return o>a}function $e(e,t){try{return e.startsWith("#")?L(e,t):e.startsWith("rgb")?ae(e,t):oe(e,t)}catch{throw new Error(`${e} is not a valid colour format. isDarkColour accepts CSS RGB formats, ie rgb(0,0,0) and rgba(255, 255, 255, 0.4), hexadecimal and CSS named colours.`)}}const se=(e,t)=>e>t?e/t:t/e,ke=({backgroundColour:e,optionOneColour:t,optionTwoColour:n},r)=>{const o=l(e,r)+.05,a=l(t,r)+.05,s=l(n,r)+.05,f=se(a,o),c=se(s,o);return f>c?t:n};export{V as RGBdistance,j as ciede2000,Ce as convertCSSRGBtoHex,i as convertCSSRGBtoRGB,te as convertHextoNamedCSSColour,u as convertHextoRGB,M as convertNamedCSSColourtoHex,R as convertNamedCSSColourtoRGB,N as convertRGBtoCSSRGB,p as convertRGBtoHex,w as convertRGBtoLab,H as convertRGBtoNamedCSSColour,O as convertRGBtoXYZ,U as convertXYZtoLab,pe as findNearestCSSRGBColour,Re as findNearestColour,Se as findNearestHexColour,Ge as findNearestNamedCSSColour,S as findNearestRGBColour,Be as getContrastRatioFromHex,l as getSRGBLuminanceFromHex,I as getSRGBLuminanceFromRGB,oe as isCSSNamedDarkColour,q as isCSSRGBColour,ae as isCSSRGBDarkColour,$e as isDarkColour,L as isHexDarkColour,ke as pickHexColourContrast};
package/dist/index.mjs CHANGED
@@ -66,42 +66,52 @@ function clamp(value, min, max) {
66
66
  return Math.min(Math.max(value, min), max);
67
67
  }
68
68
  function ciede2000(colour1, colour2) {
69
- const L1 = colour1.L;
70
- const a1 = colour1.a;
71
- const b1 = colour1.b;
72
- const L2 = colour2.L;
73
- const a2 = colour2.a;
74
- const b2 = colour2.b;
75
- const kL = 1;
76
- const kC = 1;
77
- const kH = 1;
78
- const C1 = Math.sqrt(a1 ** 2 + b1 ** 2);
79
- const C2 = Math.sqrt(a2 ** 2 + b2 ** 2);
80
- const ΔL_d = L2 - L1;
81
- const = (C1 + C2) / 2;
82
- const G = 0.5 * (1 - bigSquare());
83
- const a1_d = a1 * (1 + G);
84
- const a2_d = a2 * (1 + G);
85
- const C1_d = Math.sqrt(a1_d ** 2 + b1 ** 2);
86
- const C2_d = Math.sqrt(a2_d ** 2 + b2 ** 2);
87
- const C̅_d = (C1_d + C2_d) / 2;
88
- const ΔC̅_d = C2_d - C1_d;
89
- const h1_d = hue_d(b1, a1_d);
90
- const h2_d = hue_d(b2, a2_d);
91
- const Δh_d = deltaHue_d({ C1, C2, h1_d, h2_d });
92
- const ΔH_d = 2 * Math.sqrt(C1_d * C2_d) * Math.sin(toRadians(Δh_d) / 2);
93
- const H̅_d = meanHue_d({ C1, C2, h1_d, h2_d });
94
- const L̅ = (L1 + L2) / 2;
95
- const T = 1 - 0.17 * Math.cos(toRadians(H̅_d - 30)) + 0.24 * Math.cos(toRadians(2 * H̅_d)) + 0.32 * Math.cos(toRadians(3 * H̅_d + 6)) - 0.2 * Math.cos(toRadians(4 * H̅_d - 63));
96
- const SL = 1 + 0.015 * (L̅ - 50) ** 2 / Math.sqrt(20 + (L̅ - 50) ** 2);
97
- const SC = 0.045 * C̅_d + 1;
98
- const SH = 1 + 0.015 * C̅_d * T;
99
- const rotation = 30 * Math.exp(-(((H̅_d - 275) / 25) ** 2));
100
- const RT = -2 * bigSquare(C̅_d) * Math.sin(toRadians(rotation * 2));
101
- const ΔE = Math.sqrt(
102
- (ΔL_d / (kL * SL)) ** 2 + (ΔC̅_d / (kC * SC)) ** 2 + (ΔH_d / (kH * SH)) ** 2 + RT * (ΔC̅_d / (kC * SC)) * (ΔH_d / (kH * SH))
69
+ const lightness1 = colour1.L;
70
+ const greenRed1 = colour1.a;
71
+ const blueYellow1 = colour1.b;
72
+ const lightness2 = colour2.L;
73
+ const greenRed2 = colour2.a;
74
+ const blueYellow2 = colour2.b;
75
+ const luminanceWeight = 1;
76
+ const chromaWeight = 1;
77
+ const hueWeight = 1;
78
+ const chroma1 = Math.sqrt(greenRed1 ** 2 + blueYellow1 ** 2);
79
+ const chroma2 = Math.sqrt(greenRed2 ** 2 + blueYellow2 ** 2);
80
+ const deltaLightness = lightness2 - lightness1;
81
+ const meanChroma = (chroma1 + chroma2) / 2;
82
+ const G = 0.5 * (1 - bigSquare(meanChroma));
83
+ const greenRed1Prime = greenRed1 * (1 + G);
84
+ const greenRed2Prime = greenRed2 * (1 + G);
85
+ const chroma1Prime = Math.sqrt(greenRed1Prime ** 2 + blueYellow1 ** 2);
86
+ const chroma2Prime = Math.sqrt(greenRed2Prime ** 2 + blueYellow2 ** 2);
87
+ const meanChromaPrime = (chroma1Prime + chroma2Prime) / 2;
88
+ const deltaChromaPrime = chroma2Prime - chroma1Prime;
89
+ const hue1Prime = hue_d(blueYellow1, greenRed1Prime);
90
+ const hue2Prime = hue_d(blueYellow2, greenRed2Prime);
91
+ const deltaHuePrime = deltaHue_d({
92
+ C1: chroma1,
93
+ C2: chroma2,
94
+ h1_d: hue1Prime,
95
+ h2_d: hue2Prime
96
+ });
97
+ const deltaHue = 2 * Math.sqrt(chroma1Prime * chroma2Prime) * Math.sin(toRadians(deltaHuePrime) / 2);
98
+ const meanHuePrime = meanHue_d({
99
+ C1: chroma1,
100
+ C2: chroma2,
101
+ h1_d: hue1Prime,
102
+ h2_d: hue2Prime
103
+ });
104
+ const meanLightness = (lightness1 + lightness2) / 2;
105
+ const T = 1 - 0.17 * Math.cos(toRadians(meanHuePrime - 30)) + 0.24 * Math.cos(toRadians(2 * meanHuePrime)) + 0.32 * Math.cos(toRadians(3 * meanHuePrime + 6)) - 0.2 * Math.cos(toRadians(4 * meanHuePrime - 63));
106
+ const SL = 1 + 0.015 * (meanLightness - 50) ** 2 / Math.sqrt(20 + (meanLightness - 50) ** 2);
107
+ const SC = 0.045 * meanChromaPrime + 1;
108
+ const SH = 1 + 0.015 * meanChromaPrime * T;
109
+ const rotation = 30 * Math.exp(-(((meanHuePrime - 275) / 25) ** 2));
110
+ const RT = -2 * bigSquare(meanChromaPrime) * Math.sin(toRadians(rotation * 2));
111
+ const deltaE = Math.sqrt(
112
+ (deltaLightness / (luminanceWeight * SL)) ** 2 + (deltaChromaPrime / (chromaWeight * SC)) ** 2 + (deltaHue / (hueWeight * SH)) ** 2 + RT * (deltaChromaPrime / (chromaWeight * SC)) * (deltaHue / (hueWeight * SH))
103
113
  );
104
- return ΔE;
114
+ return deltaE;
105
115
  }
106
116
  function convertRGBtoXYZ(colour) {
107
117
  const { R, G, B } = colour;
@@ -506,6 +516,14 @@ function getSRGBLuminanceFromHex(colour, standard) {
506
516
  const rgbColor = convertHextoRGB(colour);
507
517
  return getSRGBLuminanceFromRGB(rgbColor, standard);
508
518
  }
519
+ function getContrastRatioFromHex(colour1, colour2, standard) {
520
+ const luminance1 = getSRGBLuminanceFromHex(colour1, standard);
521
+ const luminance2 = getSRGBLuminanceFromHex(colour2, standard);
522
+ const lighter = Math.max(luminance1, luminance2);
523
+ const darker = Math.min(luminance1, luminance2);
524
+ const ratio = (lighter + 0.05) / (darker + 0.05);
525
+ return Math.floor(ratio * 1e3) / 1e3;
526
+ }
509
527
  function isHexDarkColour(colour, standard) {
510
528
  const colourLuminance = getSRGBLuminanceFromHex(colour, standard) + 0.05;
511
529
  const whiteContrast = 1.05 / colourLuminance;
@@ -586,6 +604,7 @@ export {
586
604
  findNearestHexColour,
587
605
  findNearestNamedCSSColour,
588
606
  findNearestRGBColour,
607
+ getContrastRatioFromHex,
589
608
  getSRGBLuminanceFromHex,
590
609
  getSRGBLuminanceFromRGB,
591
610
  isCSSNamedDarkColour,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sardine/colour",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "description": "It does things to colours",
5
5
  "type": "module",
6
6
  "module": "./dist/index.mjs",
@@ -20,8 +20,7 @@
20
20
  "test": "npx vitest run --coverage",
21
21
  "types:emit": "npx tsc -p tsconfig.emit.json",
22
22
  "types:check": "npx tsc --noEmit",
23
- "static": "npx biome ci src",
24
- "prepare": "husky install"
23
+ "static": "npx biome ci src"
25
24
  },
26
25
  "repository": {
27
26
  "type": "git",
@@ -47,21 +46,14 @@
47
46
  "node": ">= 16"
48
47
  },
49
48
  "devDependencies": {
50
- "@biomejs/biome": "1.8.3",
49
+ "@biomejs/biome": "1.9.4",
51
50
  "@changesets/cli": "^2.27.1",
52
51
  "@tsconfig/node20": "^20.1.4",
53
52
  "@tsconfig/strictest": "^2.0.5",
54
53
  "@vitest/coverage-v8": "^2.0.0",
55
- "husky": "^9.0.11",
56
- "lint-staged": "^15.2.2",
54
+ "lefthook": "^1.8.1",
57
55
  "typescript": "^5.4.5",
58
56
  "vite": "^5.2.11",
59
57
  "vitest": "^2.0.0"
60
- },
61
- "lint-staged": {
62
- "*.{js,ts}": [
63
- "npx @biomejs/biome format --write ./src",
64
- "npx @biomejs/biome lint --apply ./src"
65
- ]
66
58
  }
67
59
  }