@studiotoolkit/color-from-image 1.0.2 → 1.0.3

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.
@@ -0,0 +1,2 @@
1
+ .cfi-container{background-color:#fff;flex-direction:column;align-items:flex-start;gap:8px;display:flex}[data-theme=dark] .cfi-container{background-color:#1a1a1a!important}.cfi-canvas-wrapper{box-sizing:border-box;border-style:solid;line-height:0;display:inline-block;position:relative}[data-theme=dark] .cfi-canvas-wrapper{border-color:#444}.cfi-canvas{display:block}.cfi-canvas-overlay{pointer-events:none;background:#f8f8f8d9;flex-direction:column;justify-content:center;align-items:center;gap:4px;line-height:normal;display:flex;position:absolute;inset:0}[data-theme=dark] .cfi-canvas-overlay{background:#1a1a1ad9}.cfi-canvas-overlay span{color:#888;font-family:inherit;font-size:14px}[data-theme=dark] .cfi-canvas-overlay span{color:#aaa}.cfi-canvas-overlay span:first-child{color:#555;font-size:16px;font-weight:600}[data-theme=dark] .cfi-canvas-overlay span:first-child{color:#ccc}.cfi-actions-row{align-items:center;gap:8px;display:flex}.cfi-file-input{flex:1;font-family:inherit;font-size:13px}.cfi-clear-button{color:#fff;cursor:pointer;white-space:nowrap;background:#e74c3c;border:none;border-radius:4px;padding:5px 12px;font-family:inherit;font-size:13px}.cfi-clear-button:hover{background:#c0392b}.cfi-paste-button{color:#fff;cursor:pointer;white-space:nowrap;background:#2196f3;border:none;border-radius:4px;padding:5px 12px;font-family:inherit;font-size:13px}.cfi-paste-button:hover{background:#1976d2}.cfi-url-row{align-items:center;gap:6px;display:flex}.cfi-url-input{border:1px solid #ccc;border-radius:4px;padding:5px 8px;font-family:inherit;font-size:13px}.cfi-load-button{color:#fff;cursor:pointer;background:#3498db;border:none;border-radius:4px;padding:5px 12px;font-family:inherit;font-size:13px}.cfi-load-button:hover{background:#217dbb}.cfi-url-error{color:#c0392b;margin:0;font-size:12px}
2
+ /*$vite$:1*/
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const w=require("react/jsx-runtime"),v=require("react"),I=t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4),j=t=>t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055,R=t=>Math.max(0,Math.min(1,t)),$=({r:t,g:r,b:p})=>{const s=I(t),m=I(r),c=I(p),u=Math.cbrt(.4122214708*s+.5363325363*m+.0514459929*c),h=Math.cbrt(.2119034982*s+.6806995451*m+.1073969566*c),b=Math.cbrt(.0883024619*s+.2817188376*m+.6299787005*c);return{L:.2104542553*u+.793617785*h-.0040720468*b,a:1.9779984951*u-2.428592205*h+.4505937099*b,b:.0259040371*u+.7827717662*h-.808675766*b}},k=({L:t,a:r,b:p})=>{const s=t+.3963377774*r+.2158037573*p,m=t-.1055613458*r-.0638541728*p,c=t-.0894841775*r-1.291485548*p,u=s*s*s,h=m*m*m,b=c*c*c;return{r:R(j(4.0767416621*u-3.3077115913*h+.2309699292*b)),g:R(j(-1.2684380046*u+2.6097574011*h-.3413193965*b)),b:R(j(-.0041960863*u-.7034186147*h+1.707614701*b))}},C=(t,r)=>(t.L-r.L)**2+(t.a-r.a)**2+(t.b-r.b)**2,O=t=>Math.sqrt(t.a*t.a+t.b*t.b),A=({r:t,g:r,b:p})=>{const s=m=>Math.round(m*255).toString(16).padStart(2,"0");return`#${s(t)}${s(r)}${s(p)}`},F=(t,r,p=20)=>{const s=[],m=Math.max(1,Math.floor(t.length/4/4e3));for(let n=0;n<t.length;n+=4*m){if(t[n+3]<128)continue;const l=$({r:t[n]/255,g:t[n+1]/255,b:t[n+2]/255}),f=O(l),e=f<.05?.2:f<.15?.6:1;Math.random()>e||s.push(l)}if(s.length===0)return[];const c=s.length,u=[];u.push(s[Math.floor(Math.random()*c)]);for(let n=1;n<r;n++){const l=s.map(d=>Math.min(...u.map(M=>C(d,M)))),f=l.reduce((d,M)=>d+M,0);let e=Math.random()*f,g=0;for(let d=0;d<c;d++)if(e-=l[d],e<=0){g=d;break}u.push({...s[g]})}const h=new Int32Array(c);for(let n=0;n<p;n++){let l=!1;for(let e=0;e<c;e++){let g=0,d=1/0;for(let M=0;M<r;M++){const D=C(s[e],u[M]);D<d&&(d=D,g=M)}h[e]!==g&&(h[e]=g,l=!0)}if(!l)break;const f=Array.from({length:r},()=>({L:0,a:0,b:0,count:0}));for(let e=0;e<c;e++){const g=h[e];f[g].L+=s[e].L,f[g].a+=s[e].a,f[g].b+=s[e].b,f[g].count++}for(let e=0;e<r;e++)f[e].count>0&&(u[e]={L:f[e].L/f[e].count,a:f[e].a/f[e].count,b:f[e].b/f[e].count})}const b=new Array(r).fill(0);for(let n=0;n<c;n++)b[h[n]]++;return u.map((n,l)=>({hex:A(k(n)),count:b[l],total:c,score:b[l]*(1+3*O(n))})).filter(n=>n.count>0).sort((n,l)=>l.score-n.score)},S=(t,r=16)=>{const p=t.naturalWidth,s=t.naturalHeight;if(!p||!s)return{palette:[]};const m=document.createElement("canvas");m.width=p,m.height=s;const c=m.getContext("2d");if(!c)return{palette:[]};c.drawImage(t,0,0);let u;try{u=c.getImageData(0,0,p,s).data}catch{return{palette:[]}}return{palette:F(u,r).map(({hex:h})=>h)}},N=({width:t=200,height:r=200,borderColor:p="#cccccc",borderThickness:s=1,onColorsExtracted:m,imageUrl:c,imageFile:u,clearTrigger:h})=>{const b=v.useRef(null),n=v.useRef(()=>{}),l=v.useRef(m),[f,e]=v.useState(null),[g,d]=v.useState(""),M=o=>{const a=b.current;if(!a)return;const i=a.getContext("2d");if(!i)return;i.clearRect(0,0,t,r);const x=Math.min(t/o.width,r/o.height),y=o.width*x,L=o.height*x;i.drawImage(o,(t-y)/2,(r-L)/2,y,L)};v.useLayoutEffect(()=>{n.current=M}),v.useLayoutEffect(()=>{l.current=m}),v.useEffect(()=>{f&&n.current(f)},[t,r,f]);const D=o=>{const a=new FileReader;a.onload=()=>{const i=new Image;i.onload=()=>{var y;e(i),d(""),n.current(i);const x=S(i);x.palette.length>0&&((y=l.current)==null||y.call(l,x))},i.src=a.result},a.readAsDataURL(o)};v.useEffect(()=>{if(!c)return;d("");const o=new Image;o.crossOrigin="anonymous",o.src=c,o.onload=()=>{var i;e(o),n.current(o);const a=S(o);a.palette.length>0?(i=l.current)==null||i.call(l,a):d("Image loaded but color extraction failed.")},o.onerror=()=>{const a=new Image;a.src=c,a.onload=()=>{e(a),n.current(a),d("Displayed but CORS blocked color extraction. Upload the file directly.")},a.onerror=()=>d("Failed to load image. Check the URL and try again.")}},[c]),v.useEffect(()=>{u&&(d(""),D(u))},[u]),v.useEffect(()=>{var a,i;if(!h)return;const o=(a=b.current)==null?void 0:a.getContext("2d");o==null||o.clearRect(0,0,t,r),e(null),d(""),(i=l.current)==null||i.call(l,{palette:[]})},[h,t,r]),v.useEffect(()=>{const o=a=>{var x;const i=(x=a.clipboardData)==null?void 0:x.items;if(i){for(let y=0;y<i.length;y++)if(i[y].type.startsWith("image")){const L=i[y].getAsFile();if(L){D(L);return}}}};return window.addEventListener("paste",o),()=>window.removeEventListener("paste",o)},[]);const E=o=>o.preventDefault(),T=o=>{var i;o.preventDefault();const a=(i=o.dataTransfer.files)==null?void 0:i[0];a!=null&&a.type.startsWith("image/")&&D(a)};return w.jsxs("div",{className:"cfi-container",children:[w.jsxs("div",{className:"cfi-canvas-wrapper",style:{borderColor:p,borderWidth:`${s}px`},onDragOver:E,onDrop:T,children:[w.jsx("canvas",{ref:b,width:t,height:r,className:"cfi-canvas",style:{width:`${t}px`,height:`${r}px`}}),!f&&w.jsxs("div",{className:"cfi-canvas-overlay",children:[w.jsx("span",{children:"Drag & Drop"}),w.jsx("span",{children:"or Ctrl+V / Paste"})]})]}),g&&w.jsx("p",{className:"cfi-url-error",children:g})]})};exports.ColorFromImage=N;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));let c=require("react");c=s(c);let l=require("react/jsx-runtime");var u=e=>e<=.04045?e/12.92:((e+.055)/1.055)**2.4,d=e=>e<=.0031308?12.92*e:1.055*e**(1/2.4)-.055,f=e=>Math.max(0,Math.min(1,e)),p=({r:e,g:t,b:n})=>{let r=u(e),i=u(t),a=u(n),o=Math.cbrt(.4122214708*r+.5363325363*i+.0514459929*a),s=Math.cbrt(.2119034982*r+.6806995451*i+.1073969566*a),c=Math.cbrt(.0883024619*r+.2817188376*i+.6299787005*a);return{L:.2104542553*o+.793617785*s-.0040720468*c,a:1.9779984951*o-2.428592205*s+.4505937099*c,b:.0259040371*o+.7827717662*s-.808675766*c}},m=({L:e,a:t,b:n})=>{let r=e+.3963377774*t+.2158037573*n,i=e-.1055613458*t-.0638541728*n,a=e-.0894841775*t-1.291485548*n,o=r*r*r,s=i*i*i,c=a*a*a;return{r:f(d(4.0767416621*o-3.3077115913*s+.2309699292*c)),g:f(d(-1.2684380046*o+2.6097574011*s-.3413193965*c)),b:f(d(-.0041960863*o-.7034186147*s+1.707614701*c))}},h=(e,t)=>(e.L-t.L)**2+(e.a-t.a)**2+(e.b-t.b)**2,g=e=>Math.sqrt(e.a*e.a+e.b*e.b),_=({r:e,g:t,b:n})=>{let r=e=>Math.round(e*255).toString(16).padStart(2,`0`);return`#${r(e)}${r(t)}${r(n)}`},v=(e,t,n=20)=>{let r=[],i=Math.max(1,Math.floor(e.length/4/4e3));for(let t=0;t<e.length;t+=4*i){if(e[t+3]<128)continue;let n=p({r:e[t]/255,g:e[t+1]/255,b:e[t+2]/255}),i=g(n);Math.random()>(i<.05?.2:i<.15?.6:1)||r.push(n)}if(r.length===0)return[];let a=r.length,o=[];o.push(r[Math.floor(Math.random()*a)]);for(let e=1;e<t;e++){let e=r.map(e=>Math.min(...o.map(t=>h(e,t)))),t=e.reduce((e,t)=>e+t,0),n=Math.random()*t,i=0;for(let t=0;t<a;t++)if(n-=e[t],n<=0){i=t;break}o.push({...r[i]})}let s=new Int32Array(a);for(let e=0;e<n;e++){let e=!1;for(let n=0;n<a;n++){let i=0,a=1/0;for(let e=0;e<t;e++){let t=h(r[n],o[e]);t<a&&(a=t,i=e)}s[n]!==i&&(s[n]=i,e=!0)}if(!e)break;let n=Array.from({length:t},()=>({L:0,a:0,b:0,count:0}));for(let e=0;e<a;e++){let t=s[e];n[t].L+=r[e].L,n[t].a+=r[e].a,n[t].b+=r[e].b,n[t].count++}for(let e=0;e<t;e++)n[e].count>0&&(o[e]={L:n[e].L/n[e].count,a:n[e].a/n[e].count,b:n[e].b/n[e].count})}let c=Array(t).fill(0);for(let e=0;e<a;e++)c[s[e]]++;return o.map((e,t)=>({hex:_(m(e)),count:c[t],total:a,score:c[t]*(1+3*g(e))})).filter(e=>e.count>0).sort((e,t)=>t.score-e.score)},y=(e,t=16)=>{let n=e.naturalWidth,r=e.naturalHeight;if(!n||!r)return{palette:[]};let i=document.createElement(`canvas`);i.width=n,i.height=r;let a=i.getContext(`2d`);if(!a)return{palette:[]};a.drawImage(e,0,0);let o;try{o=a.getImageData(0,0,n,r).data}catch{return{palette:[]}}return{palette:v(o,t).map(({hex:e})=>e)}},b=({width:e=200,height:t=200,borderColor:n=`#cccccc`,borderThickness:r=1,onColorsExtracted:i,imageUrl:a,imageFile:o,clearTrigger:s})=>{let u=(0,c.useRef)(null),d=(0,c.useRef)(()=>{}),f=(0,c.useRef)(i),[p,m]=(0,c.useState)(null),[h,g]=(0,c.useState)(``),_=n=>{let r=u.current;if(!r)return;let i=r.getContext(`2d`);if(!i)return;i.clearRect(0,0,e,t);let a=Math.min(e/n.width,t/n.height),o=n.width*a,s=n.height*a;i.drawImage(n,(e-o)/2,(t-s)/2,o,s)};(0,c.useLayoutEffect)(()=>{d.current=_}),(0,c.useLayoutEffect)(()=>{f.current=i}),(0,c.useEffect)(()=>{p&&d.current(p)},[e,t,p]);let v=e=>{let t=new FileReader;t.onload=()=>{let e=new Image;e.onload=()=>{m(e),g(``),d.current(e);let t=y(e);t.palette.length>0&&f.current?.(t)},e.src=t.result},t.readAsDataURL(e)};return(0,c.useEffect)(()=>{if(!a)return;g(``);let e=new Image;e.crossOrigin=`anonymous`,e.src=a,e.onload=()=>{m(e),d.current(e);let t=y(e);t.palette.length>0?f.current?.(t):g(`Image loaded but color extraction failed.`)},e.onerror=()=>{let e=new Image;e.src=a,e.onload=()=>{m(e),d.current(e),g(`Displayed but CORS blocked color extraction. Upload the file directly.`)},e.onerror=()=>g(`Failed to load image. Check the URL and try again.`)}},[a]),(0,c.useEffect)(()=>{o&&(g(``),v(o))},[o]),(0,c.useEffect)(()=>{s&&((u.current?.getContext(`2d`))?.clearRect(0,0,e,t),m(null),g(``),f.current?.({palette:[]}))},[s,e,t]),(0,c.useEffect)(()=>{let e=e=>{let t=e.clipboardData?.items;if(t){for(let e=0;e<t.length;e++)if(t[e].type.startsWith(`image`)){let n=t[e].getAsFile();if(n){v(n);return}}}};return window.addEventListener(`paste`,e),()=>window.removeEventListener(`paste`,e)},[]),(0,l.jsxs)(`div`,{className:`cfi-container`,children:[(0,l.jsxs)(`div`,{className:`cfi-canvas-wrapper`,style:{borderColor:n,borderWidth:`${r}px`},onDragOver:e=>e.preventDefault(),onDrop:e=>{e.preventDefault();let t=e.dataTransfer.files?.[0];t?.type.startsWith(`image/`)&&v(t)},children:[(0,l.jsx)(`canvas`,{ref:u,width:e,height:t,className:`cfi-canvas`,style:{width:`${e}px`,height:`${t}px`}}),!p&&(0,l.jsxs)(`div`,{className:`cfi-canvas-overlay`,children:[(0,l.jsx)(`span`,{children:`Drag & Drop`}),(0,l.jsx)(`span`,{children:`or Ctrl+V / Paste`})]})]}),h&&(0,l.jsx)(`p`,{className:`cfi-url-error`,children:h})]})};exports.ColorFromImage=b;
package/dist/index.mjs CHANGED
@@ -1,215 +1,195 @@
1
- import { jsxs as I, jsx as L } from "react/jsx-runtime";
2
- import { useRef as C, useState as A, useLayoutEffect as N, useEffect as x } from "react";
3
- const O = (t) => t <= 0.04045 ? t / 12.92 : Math.pow((t + 0.055) / 1.055, 2.4), $ = (t) => t <= 31308e-7 ? 12.92 * t : 1.055 * Math.pow(t, 1 / 2.4) - 0.055, k = (t) => Math.max(0, Math.min(1, t)), j = ({ r: t, g: r, b: p }) => {
4
- const o = O(t), m = O(r), c = O(p), u = Math.cbrt(0.4122214708 * o + 0.5363325363 * m + 0.0514459929 * c), h = Math.cbrt(0.2119034982 * o + 0.6806995451 * m + 0.1073969566 * c), b = Math.cbrt(0.0883024619 * o + 0.2817188376 * m + 0.6299787005 * c);
5
- return {
6
- L: 0.2104542553 * u + 0.793617785 * h - 0.0040720468 * b,
7
- a: 1.9779984951 * u - 2.428592205 * h + 0.4505937099 * b,
8
- b: 0.0259040371 * u + 0.7827717662 * h - 0.808675766 * b
9
- };
10
- }, z = ({ L: t, a: r, b: p }) => {
11
- const o = t + 0.3963377774 * r + 0.2158037573 * p, m = t - 0.1055613458 * r - 0.0638541728 * p, c = t - 0.0894841775 * r - 1.291485548 * p, u = o * o * o, h = m * m * m, b = c * c * c;
12
- return {
13
- r: k($(4.0767416621 * u - 3.3077115913 * h + 0.2309699292 * b)),
14
- g: k($(-1.2684380046 * u + 2.6097574011 * h - 0.3413193965 * b)),
15
- b: k($(-0.0041960863 * u - 0.7034186147 * h + 1.707614701 * b))
16
- };
17
- }, R = (t, r) => (t.L - r.L) ** 2 + (t.a - r.a) ** 2 + (t.b - r.b) ** 2, T = (t) => Math.sqrt(t.a * t.a + t.b * t.b), G = ({ r: t, g: r, b: p }) => {
18
- const o = (m) => Math.round(m * 255).toString(16).padStart(2, "0");
19
- return `#${o(t)}${o(r)}${o(p)}`;
20
- }, H = (t, r, p = 20) => {
21
- const o = [], m = Math.max(1, Math.floor(t.length / 4 / 4e3));
22
- for (let n = 0; n < t.length; n += 4 * m) {
23
- if (t[n + 3] < 128) continue;
24
- const l = j({
25
- r: t[n] / 255,
26
- g: t[n + 1] / 255,
27
- b: t[n + 2] / 255
28
- }), f = T(l), e = f < 0.05 ? 0.2 : f < 0.15 ? 0.6 : 1;
29
- Math.random() > e || o.push(l);
30
- }
31
- if (o.length === 0) return [];
32
- const c = o.length, u = [];
33
- u.push(o[Math.floor(Math.random() * c)]);
34
- for (let n = 1; n < r; n++) {
35
- const l = o.map((d) => Math.min(...u.map((v) => R(d, v)))), f = l.reduce((d, v) => d + v, 0);
36
- let e = Math.random() * f, g = 0;
37
- for (let d = 0; d < c; d++)
38
- if (e -= l[d], e <= 0) {
39
- g = d;
40
- break;
41
- }
42
- u.push({ ...o[g] });
43
- }
44
- const h = new Int32Array(c);
45
- for (let n = 0; n < p; n++) {
46
- let l = !1;
47
- for (let e = 0; e < c; e++) {
48
- let g = 0, d = 1 / 0;
49
- for (let v = 0; v < r; v++) {
50
- const y = R(o[e], u[v]);
51
- y < d && (d = y, g = v);
52
- }
53
- h[e] !== g && (h[e] = g, l = !0);
54
- }
55
- if (!l) break;
56
- const f = Array.from({ length: r }, () => ({
57
- L: 0,
58
- a: 0,
59
- b: 0,
60
- count: 0
61
- }));
62
- for (let e = 0; e < c; e++) {
63
- const g = h[e];
64
- f[g].L += o[e].L, f[g].a += o[e].a, f[g].b += o[e].b, f[g].count++;
65
- }
66
- for (let e = 0; e < r; e++)
67
- f[e].count > 0 && (u[e] = {
68
- L: f[e].L / f[e].count,
69
- a: f[e].a / f[e].count,
70
- b: f[e].b / f[e].count
71
- });
72
- }
73
- const b = new Array(r).fill(0);
74
- for (let n = 0; n < c; n++) b[h[n]]++;
75
- return u.map((n, l) => ({
76
- hex: G(z(n)),
77
- count: b[l],
78
- total: c,
79
- // Vivid centroids score higher than their raw pixel count alone
80
- score: b[l] * (1 + 3 * T(n))
81
- })).filter((n) => n.count > 0).sort((n, l) => l.score - n.score);
82
- }, F = (t, r = 16) => {
83
- const p = t.naturalWidth, o = t.naturalHeight;
84
- if (!p || !o) return { palette: [] };
85
- const m = document.createElement("canvas");
86
- m.width = p, m.height = o;
87
- const c = m.getContext("2d");
88
- if (!c) return { palette: [] };
89
- c.drawImage(t, 0, 0);
90
- let u;
91
- try {
92
- u = c.getImageData(0, 0, p, o).data;
93
- } catch {
94
- return { palette: [] };
95
- }
96
- return { palette: H(u, r).map(({ hex: h }) => h) };
97
- }, B = ({
98
- width: t = 200,
99
- height: r = 200,
100
- borderColor: p = "#cccccc",
101
- borderThickness: o = 1,
102
- onColorsExtracted: m,
103
- imageUrl: c,
104
- imageFile: u,
105
- clearTrigger: h
106
- }) => {
107
- const b = C(null), n = C(() => {
108
- }), l = C(m), [f, e] = A(null), [g, d] = A(""), v = (a) => {
109
- const s = b.current;
110
- if (!s) return;
111
- const i = s.getContext("2d");
112
- if (!i) return;
113
- i.clearRect(0, 0, t, r);
114
- const w = Math.min(t / a.width, r / a.height), M = a.width * w, D = a.height * w;
115
- i.drawImage(a, (t - M) / 2, (r - D) / 2, M, D);
116
- };
117
- N(() => {
118
- n.current = v;
119
- }), N(() => {
120
- l.current = m;
121
- }), x(() => {
122
- f && n.current(f);
123
- }, [t, r, f]);
124
- const y = (a) => {
125
- const s = new FileReader();
126
- s.onload = () => {
127
- const i = new Image();
128
- i.onload = () => {
129
- var M;
130
- e(i), d(""), n.current(i);
131
- const w = F(i);
132
- w.palette.length > 0 && ((M = l.current) == null || M.call(l, w));
133
- }, i.src = s.result;
134
- }, s.readAsDataURL(a);
135
- };
136
- x(() => {
137
- if (!c) return;
138
- d("");
139
- const a = new Image();
140
- a.crossOrigin = "anonymous", a.src = c, a.onload = () => {
141
- var i;
142
- e(a), n.current(a);
143
- const s = F(a);
144
- s.palette.length > 0 ? (i = l.current) == null || i.call(l, s) : d("Image loaded but color extraction failed.");
145
- }, a.onerror = () => {
146
- const s = new Image();
147
- s.src = c, s.onload = () => {
148
- e(s), n.current(s), d("Displayed but CORS blocked color extraction. Upload the file directly.");
149
- }, s.onerror = () => d("Failed to load image. Check the URL and try again.");
150
- };
151
- }, [c]), x(() => {
152
- u && (d(""), y(u));
153
- }, [u]), x(() => {
154
- var s, i;
155
- if (!h) return;
156
- const a = (s = b.current) == null ? void 0 : s.getContext("2d");
157
- a == null || a.clearRect(0, 0, t, r), e(null), d(""), (i = l.current) == null || i.call(l, { palette: [] });
158
- }, [h, t, r]), x(() => {
159
- const a = (s) => {
160
- var w;
161
- const i = (w = s.clipboardData) == null ? void 0 : w.items;
162
- if (i) {
163
- for (let M = 0; M < i.length; M++)
164
- if (i[M].type.startsWith("image")) {
165
- const D = i[M].getAsFile();
166
- if (D) {
167
- y(D);
168
- return;
169
- }
170
- }
171
- }
172
- };
173
- return window.addEventListener("paste", a), () => window.removeEventListener("paste", a);
174
- }, []);
175
- const S = (a) => a.preventDefault(), W = (a) => {
176
- var i;
177
- a.preventDefault();
178
- const s = (i = a.dataTransfer.files) == null ? void 0 : i[0];
179
- s != null && s.type.startsWith("image/") && y(s);
180
- };
181
- return /* @__PURE__ */ I("div", { className: "cfi-container", children: [
182
- /* @__PURE__ */ I(
183
- "div",
184
- {
185
- className: "cfi-canvas-wrapper",
186
- style: {
187
- borderColor: p,
188
- borderWidth: `${o}px`
189
- },
190
- onDragOver: S,
191
- onDrop: W,
192
- children: [
193
- /* @__PURE__ */ L(
194
- "canvas",
195
- {
196
- ref: b,
197
- width: t,
198
- height: r,
199
- className: "cfi-canvas",
200
- style: { width: `${t}px`, height: `${r}px` }
201
- }
202
- ),
203
- !f && /* @__PURE__ */ I("div", { className: "cfi-canvas-overlay", children: [
204
- /* @__PURE__ */ L("span", { children: "Drag & Drop" }),
205
- /* @__PURE__ */ L("span", { children: "or Ctrl+V / Paste" })
206
- ] })
207
- ]
208
- }
209
- ),
210
- g && /* @__PURE__ */ L("p", { className: "cfi-url-error", children: g })
211
- ] });
212
- };
213
- export {
214
- B as ColorFromImage
1
+ import { useEffect as e, useLayoutEffect as t, useRef as n, useState as r } from "react";
2
+ import { jsx as i, jsxs as a } from "react/jsx-runtime";
3
+ // src/ColorFromImage.tsx
4
+ var o = (e) => e <= .04045 ? e / 12.92 : ((e + .055) / 1.055) ** 2.4, s = (e) => e <= .0031308 ? 12.92 * e : 1.055 * e ** (1 / 2.4) - .055, c = (e) => Math.max(0, Math.min(1, e)), l = ({ r: e, g: t, b: n }) => {
5
+ let r = o(e), i = o(t), a = o(n), s = Math.cbrt(.4122214708 * r + .5363325363 * i + .0514459929 * a), c = Math.cbrt(.2119034982 * r + .6806995451 * i + .1073969566 * a), l = Math.cbrt(.0883024619 * r + .2817188376 * i + .6299787005 * a);
6
+ return {
7
+ L: .2104542553 * s + .793617785 * c - .0040720468 * l,
8
+ a: 1.9779984951 * s - 2.428592205 * c + .4505937099 * l,
9
+ b: .0259040371 * s + .7827717662 * c - .808675766 * l
10
+ };
11
+ }, u = ({ L: e, a: t, b: n }) => {
12
+ let r = e + .3963377774 * t + .2158037573 * n, i = e - .1055613458 * t - .0638541728 * n, a = e - .0894841775 * t - 1.291485548 * n, o = r * r * r, l = i * i * i, u = a * a * a;
13
+ return {
14
+ r: c(s(4.0767416621 * o - 3.3077115913 * l + .2309699292 * u)),
15
+ g: c(s(-1.2684380046 * o + 2.6097574011 * l - .3413193965 * u)),
16
+ b: c(s(-.0041960863 * o - .7034186147 * l + 1.707614701 * u))
17
+ };
18
+ }, d = (e, t) => (e.L - t.L) ** 2 + (e.a - t.a) ** 2 + (e.b - t.b) ** 2, f = (e) => Math.sqrt(e.a * e.a + e.b * e.b), p = ({ r: e, g: t, b: n }) => {
19
+ let r = (e) => Math.round(e * 255).toString(16).padStart(2, "0");
20
+ return `#${r(e)}${r(t)}${r(n)}`;
21
+ }, m = (e, t, n = 20) => {
22
+ let r = [], i = Math.max(1, Math.floor(e.length / 4 / 4e3));
23
+ for (let t = 0; t < e.length; t += 4 * i) {
24
+ if (e[t + 3] < 128) continue;
25
+ let n = l({
26
+ r: e[t] / 255,
27
+ g: e[t + 1] / 255,
28
+ b: e[t + 2] / 255
29
+ }), i = f(n);
30
+ Math.random() > (i < .05 ? .2 : i < .15 ? .6 : 1) || r.push(n);
31
+ }
32
+ if (r.length === 0) return [];
33
+ let a = r.length, o = [];
34
+ o.push(r[Math.floor(Math.random() * a)]);
35
+ for (let e = 1; e < t; e++) {
36
+ let e = r.map((e) => Math.min(...o.map((t) => d(e, t)))), t = e.reduce((e, t) => e + t, 0), n = Math.random() * t, i = 0;
37
+ for (let t = 0; t < a; t++) if (n -= e[t], n <= 0) {
38
+ i = t;
39
+ break;
40
+ }
41
+ o.push({ ...r[i] });
42
+ }
43
+ let s = new Int32Array(a);
44
+ for (let e = 0; e < n; e++) {
45
+ let e = !1;
46
+ for (let n = 0; n < a; n++) {
47
+ let i = 0, a = Infinity;
48
+ for (let e = 0; e < t; e++) {
49
+ let t = d(r[n], o[e]);
50
+ t < a && (a = t, i = e);
51
+ }
52
+ s[n] !== i && (s[n] = i, e = !0);
53
+ }
54
+ if (!e) break;
55
+ let n = Array.from({ length: t }, () => ({
56
+ L: 0,
57
+ a: 0,
58
+ b: 0,
59
+ count: 0
60
+ }));
61
+ for (let e = 0; e < a; e++) {
62
+ let t = s[e];
63
+ n[t].L += r[e].L, n[t].a += r[e].a, n[t].b += r[e].b, n[t].count++;
64
+ }
65
+ for (let e = 0; e < t; e++) n[e].count > 0 && (o[e] = {
66
+ L: n[e].L / n[e].count,
67
+ a: n[e].a / n[e].count,
68
+ b: n[e].b / n[e].count
69
+ });
70
+ }
71
+ let c = Array(t).fill(0);
72
+ for (let e = 0; e < a; e++) c[s[e]]++;
73
+ return o.map((e, t) => ({
74
+ hex: p(u(e)),
75
+ count: c[t],
76
+ total: a,
77
+ score: c[t] * (1 + 3 * f(e))
78
+ })).filter((e) => e.count > 0).sort((e, t) => t.score - e.score);
79
+ }, h = (e, t = 16) => {
80
+ let n = e.naturalWidth, r = e.naturalHeight;
81
+ if (!n || !r) return { palette: [] };
82
+ let i = document.createElement("canvas");
83
+ i.width = n, i.height = r;
84
+ let a = i.getContext("2d");
85
+ if (!a) return { palette: [] };
86
+ a.drawImage(e, 0, 0);
87
+ let o;
88
+ try {
89
+ o = a.getImageData(0, 0, n, r).data;
90
+ } catch {
91
+ return { palette: [] };
92
+ }
93
+ return { palette: m(o, t).map(({ hex: e }) => e) };
94
+ }, g = ({ width: o = 200, height: s = 200, borderColor: c = "#cccccc", borderThickness: l = 1, onColorsExtracted: u, imageUrl: d, imageFile: f, clearTrigger: p }) => {
95
+ let m = n(null), g = n(() => {}), _ = n(u), [v, y] = r(null), [b, x] = r(""), S = (e) => {
96
+ let t = m.current;
97
+ if (!t) return;
98
+ let n = t.getContext("2d");
99
+ if (!n) return;
100
+ n.clearRect(0, 0, o, s);
101
+ let r = Math.min(o / e.width, s / e.height), i = e.width * r, a = e.height * r;
102
+ n.drawImage(e, (o - i) / 2, (s - a) / 2, i, a);
103
+ };
104
+ t(() => {
105
+ g.current = S;
106
+ }), t(() => {
107
+ _.current = u;
108
+ }), e(() => {
109
+ v && g.current(v);
110
+ }, [
111
+ o,
112
+ s,
113
+ v
114
+ ]);
115
+ let C = (e) => {
116
+ let t = new FileReader();
117
+ t.onload = () => {
118
+ let e = new Image();
119
+ e.onload = () => {
120
+ y(e), x(""), g.current(e);
121
+ let t = h(e);
122
+ t.palette.length > 0 && _.current?.(t);
123
+ }, e.src = t.result;
124
+ }, t.readAsDataURL(e);
125
+ };
126
+ return e(() => {
127
+ if (!d) return;
128
+ x("");
129
+ let e = new Image();
130
+ e.crossOrigin = "anonymous", e.src = d, e.onload = () => {
131
+ y(e), g.current(e);
132
+ let t = h(e);
133
+ t.palette.length > 0 ? _.current?.(t) : x("Image loaded but color extraction failed.");
134
+ }, e.onerror = () => {
135
+ let e = new Image();
136
+ e.src = d, e.onload = () => {
137
+ y(e), g.current(e), x("Displayed but CORS blocked color extraction. Upload the file directly.");
138
+ }, e.onerror = () => x("Failed to load image. Check the URL and try again.");
139
+ };
140
+ }, [d]), e(() => {
141
+ f && (x(""), C(f));
142
+ }, [f]), e(() => {
143
+ p && ((m.current?.getContext("2d"))?.clearRect(0, 0, o, s), y(null), x(""), _.current?.({ palette: [] }));
144
+ }, [
145
+ p,
146
+ o,
147
+ s
148
+ ]), e(() => {
149
+ let e = (e) => {
150
+ let t = e.clipboardData?.items;
151
+ if (t) {
152
+ for (let e = 0; e < t.length; e++) if (t[e].type.startsWith("image")) {
153
+ let n = t[e].getAsFile();
154
+ if (n) {
155
+ C(n);
156
+ return;
157
+ }
158
+ }
159
+ }
160
+ };
161
+ return window.addEventListener("paste", e), () => window.removeEventListener("paste", e);
162
+ }, []), /* @__PURE__ */ a("div", {
163
+ className: "cfi-container",
164
+ children: [/* @__PURE__ */ a("div", {
165
+ className: "cfi-canvas-wrapper",
166
+ style: {
167
+ borderColor: c,
168
+ borderWidth: `${l}px`
169
+ },
170
+ onDragOver: (e) => e.preventDefault(),
171
+ onDrop: (e) => {
172
+ e.preventDefault();
173
+ let t = e.dataTransfer.files?.[0];
174
+ t?.type.startsWith("image/") && C(t);
175
+ },
176
+ children: [/* @__PURE__ */ i("canvas", {
177
+ ref: m,
178
+ width: o,
179
+ height: s,
180
+ className: "cfi-canvas",
181
+ style: {
182
+ width: `${o}px`,
183
+ height: `${s}px`
184
+ }
185
+ }), !v && /* @__PURE__ */ a("div", {
186
+ className: "cfi-canvas-overlay",
187
+ children: [/* @__PURE__ */ i("span", { children: "Drag & Drop" }), /* @__PURE__ */ i("span", { children: "or Ctrl+V / Paste" })]
188
+ })]
189
+ }), b && /* @__PURE__ */ i("p", {
190
+ className: "cfi-url-error",
191
+ children: b
192
+ })]
193
+ });
215
194
  };
195
+ export { g as ColorFromImage };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@studiotoolkit/color-from-image",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "license": "MIT",
5
5
  "description": "React component for extracting dominant colors from images using advanced color analysis algorithms",
6
6
  "author": "StudioToolkit",
@@ -53,17 +53,6 @@
53
53
  "engines": {
54
54
  "node": ">=16.0.0"
55
55
  },
56
- "peerDependencies": {
57
- "react": "^18.0.0 || ^19.0.0",
58
- "react-dom": "^18.0.0 || ^19.0.0"
59
- },
60
- "devDependencies": {
61
- "@types/react": "^18.2.0 || ^19.0.0",
62
- "@types/react-dom": "^18.2.0 || ^19.0.0",
63
- "@vitejs/plugin-react": "^4.2.0",
64
- "typescript": "^5.3.3",
65
- "vite": "^5.0.0"
66
- },
67
56
  "scripts": {
68
57
  "build": "npx tsc --project tsconfig.json && vite build && cp src/ColorFromImage.css dist/style.css",
69
58
  "dev": "vite build --watch",
@@ -72,6 +61,20 @@
72
61
  "lint:fix": "eslint src --ext .ts,.tsx --fix",
73
62
  "test": "vitest run",
74
63
  "test:watch": "vitest",
75
- "test:ui": "vitest --ui"
64
+ "test:ui": "vitest --ui",
65
+ "prepublishOnly": "pnpm run build && pnpm test"
66
+ },
67
+ "peerDependencies": {
68
+ "react": "^18.0.0 || ^19.0.0",
69
+ "react-dom": "^18.0.0 || ^19.0.0"
70
+ },
71
+ "devDependencies": {
72
+ "@types/react": "^19.3.0",
73
+ "@types/react-dom": "^19.3.0",
74
+ "@vitejs/plugin-react": "^6.2.1",
75
+ "react": "^19.3.1",
76
+ "react-dom": "^19.3.1",
77
+ "typescript": "^6.0.3",
78
+ "vite": "^8.3.6"
76
79
  }
77
- }
80
+ }