UNPKG

@mtillmann/circlepacker

Version:

simple circle-packing library

2 lines (1 loc) 7.08 kB
var t={pack(t,i,s,a){let e=s.length;const n=[],o={width:i,height:t.data.length/i/4};for(;e>0;){e--;const i=s[e];let r=1e3;for(;!i.x&&r-- >0;){const s=Math.random()*o.width,e=Math.random()*o.height;this.isCircleInside(t,o.width,s,e,i.radius,a.higherAccuracy,a.minAlpha)&&(this.touchesPlacedCircle(s,e,i.radius,n,a.spacing)||(i.x=s,i.y=e,n.push(i)))}}return n},isFilled:(t,i,s,a,e)=>(i=Math.round(i),s=Math.round(s),t.data[4*(a*s+i)+3]>e),isCircleInside(t,i,s,a,e,n,o){if(!this.isFilled(t,s,a-e,i,o))return!1;if(!this.isFilled(t,s,a+e,i,o))return!1;if(!this.isFilled(t,s+e,a,i,o))return!1;if(!this.isFilled(t,s-e,a,i,o))return!1;if(n){const e=Math.cos(Math.PI/4);if(!this.isFilled(t,s+e,a+e,i,o))return!1;if(!this.isFilled(t,s-e,a+e,i,o))return!1;if(!this.isFilled(t,s-e,a-e,i,o))return!1;if(!this.isFilled(t,s+e,a-e,i,o))return!1}return!0},touchesPlacedCircle(t,i,s,a,e){return a.some((a=>this.dist(t,i,a.x,a.y)<a.radius+s+e))},dist(t,i,s,a){const e=t-s,n=i-a;return Math.sqrt(e*e+n*n)}},i=class{defaultOptions={spacing:1,numCircles:1e3,minRadius:1,maxRadius:10,higherAccuracy:!1,colors:"auto",minAlpha:1,background:"transparent",useMainThread:!1,reuseWorker:!0};defaultExportOptions={scale:globalThis.devicePixelRatio||1,quality:1,format:"image/png"};options;spareCircles=[];placedCircles=[];dims={width:0,height:0};worker=null;constructor(t={}){this.options={...this.defaultOptions,...t},["transparent",null,"",!1,void 0].includes(this.options.background)&&(this.options.background=!1),"auto"===this.options.colors&&(this.options.colors=[]);for(let t=0;t<this.options.numCircles;t++)this.spareCircles.push({radius:this.options.minRadius+Math.random()*Math.random()*(this.options.maxRadius-this.options.minRadius)});this.spareCircles.sort(((t,i)=>t.radius-i.radius))}getCircleColor(t,i,s){if(0===this.options.colors.length){i=Math.min(Math.round(i),this.dims.width-1),s=Math.round(s);const a=4*(this.dims.width*s+i);if(t.data[a+3]/255<this.options.minAlpha)return!1;return`rgb(${t.data[a]},${t.data[a+1]},${t.data[a+2]})`}return this.options.colors[Math.floor(Math.random()*this.options.colors.length)]}async pack(i,s){let a=[];return"undefined"==typeof Worker||this.options.useMainThread?a=t.pack(i,s,this.spareCircles,this.options):(this.worker||(this.worker=new Worker("data:text/javascript;base64,dmFyIENvcmVfZGVmYXVsdD17cGFjayhpLHQsZSxzKXtsZXQgcj1lLmxlbmd0aDtjb25zdCBhPVtdLGQ9e3dpZHRoOnQsaGVpZ2h0OmkuZGF0YS5sZW5ndGgvdC80fTtmb3IoO3I+MDspe3ItLTtjb25zdCB0PWVbcl07bGV0IGg9MWUzO2Zvcig7IXQueCYmaC0tID4wOyl7Y29uc3QgZT1NYXRoLnJhbmRvbSgpKmQud2lkdGgscj1NYXRoLnJhbmRvbSgpKmQuaGVpZ2h0O3RoaXMuaXNDaXJjbGVJbnNpZGUoaSxkLndpZHRoLGUscix0LnJhZGl1cyxzLmhpZ2hlckFjY3VyYWN5LHMubWluQWxwaGEpJiYodGhpcy50b3VjaGVzUGxhY2VkQ2lyY2xlKGUscix0LnJhZGl1cyxhLHMuc3BhY2luZyl8fCh0Lng9ZSx0Lnk9cixhLnB1c2godCkpKX19cmV0dXJuIGF9LGlzRmlsbGVkOihpLHQsZSxzLHIpPT4odD1NYXRoLnJvdW5kKHQpLGU9TWF0aC5yb3VuZChlKSxpLmRhdGFbNCoocyplK3QpKzNdPnIpLGlzQ2lyY2xlSW5zaWRlKGksdCxlLHMscixhLGQpe2lmKCF0aGlzLmlzRmlsbGVkKGksZSxzLXIsdCxkKSlyZXR1cm4hMTtpZighdGhpcy5pc0ZpbGxlZChpLGUscytyLHQsZCkpcmV0dXJuITE7aWYoIXRoaXMuaXNGaWxsZWQoaSxlK3Iscyx0LGQpKXJldHVybiExO2lmKCF0aGlzLmlzRmlsbGVkKGksZS1yLHMsdCxkKSlyZXR1cm4hMTtpZihhKXtjb25zdCByPU1hdGguY29zKE1hdGguUEkvNCk7aWYoIXRoaXMuaXNGaWxsZWQoaSxlK3IscytyLHQsZCkpcmV0dXJuITE7aWYoIXRoaXMuaXNGaWxsZWQoaSxlLXIscytyLHQsZCkpcmV0dXJuITE7aWYoIXRoaXMuaXNGaWxsZWQoaSxlLXIscy1yLHQsZCkpcmV0dXJuITE7aWYoIXRoaXMuaXNGaWxsZWQoaSxlK3Iscy1yLHQsZCkpcmV0dXJuITF9cmV0dXJuITB9LHRvdWNoZXNQbGFjZWRDaXJjbGUoaSx0LGUscyxyKXtyZXR1cm4gcy5zb21lKChzPT50aGlzLmRpc3QoaSx0LHMueCxzLnkpPHMucmFkaXVzK2UrcikpfSxkaXN0KGksdCxlLHMpe2NvbnN0IHI9aS1lLGE9dC1zO3JldHVybiBNYXRoLnNxcnQocipyK2EqYSl9fTtzZWxmLm9ubWVzc2FnZT1pPT57cG9zdE1lc3NhZ2UoQ29yZV9kZWZhdWx0LnBhY2soaS5kYXRhLmltYWdlRGF0YSxpLmRhdGEuaW1hZ2VXaWR0aCxpLmRhdGEuc3BhcmVDaXJjbGVzLGkuZGF0YS5vcHRpb25zKSl9Ow==")),a=await new Promise((t=>{this.worker.onmessage=i=>t(i.data),this.worker.postMessage({imageData:i,imageWidth:s,spareCircles:this.spareCircles,options:this.options})})),this.options.reuseWorker||this.worker.terminate()),this.dims={width:s,height:i.data.length/s/4},this.placedCircles=a.reduce(((t,s)=>{const a=this.getCircleColor(i,s.x,s.y);return a&&(s.color=a,t.push(s)),t}),[]),this.placedCircles}asSVGString(){return`<svg width="${this.dims.width}" height="${this.dims.height}" viewBox="0 0 ${this.dims.width} ${this.dims.height}" xmlns="http://www.w3.org/2000/svg">`+(this.options.background?`<rect x="0" y="0" width="${this.dims.width}" height="${this.dims.height}" fill="${this.options.background}" />`:"")+this.placedCircles.map((t=>{const{x:i,y:s,radius:a,color:e}=t;return`<circle cx="${i}" cy="${s}" r="${a}" fill="${e}" />`})).join("")+"</svg>"}asSVG(){const t=this.asSVGString();return(new DOMParser).parseFromString(t,"image/svg+xml").documentElement}asCanvas(t={}){const i=(t={...this.defaultExportOptions,...t}).scale,s=document.createElement("canvas");s.width=this.dims.width*i,s.height=this.dims.height*i;const a=s.getContext("2d");this.options.background&&(a.fillStyle=this.options.background,a.fillRect(0,0,s.width,s.height));for(const t of this.placedCircles){const{x:s,y:e,radius:n,color:o}=t;a.fillStyle=String(o),a.beginPath(),a.arc(Number(s*i),Number(e*i),n*i,0,2*Math.PI),a.closePath(),a.fill()}return s}asImageData(t={}){t={...this.defaultExportOptions,...t};const i=this.asCanvas(t);return i.getContext("2d").getImageData(0,0,i.width,i.height)}async asBlob(t={}){t={...this.defaultExportOptions,...t};const i=this.asCanvas(t);return await new Promise(((s,a)=>{i.toBlob((t=>{t?s(t):a(new Error("Blob creation failed"))}),t.format,t.quality)}))}async asBlobURL(t={}){return URL.createObjectURL(await this.asBlob(t))}asDataURL(t={}){return t={...this.defaultExportOptions,...t},this.asCanvas().toDataURL(t.format,t.quality)}asArray(){return this.placedCircles}};async function s(t,i={}){const s=URL.createObjectURL(t);return await a(s,i)}async function a(t,i={}){t=t instanceof URL?t.href:t;const s=document.createElement("img");return await new Promise((i=>{s.onload=i,s.src=t})),await e(s,i)}async function e(t,i={}){const s=document.createElement("canvas");s.width=t.width,s.height=t.height;const a=s.getContext("2d");return a.drawImage(t,0,0),await o(a,i)}async function n(t,s,a={}){const e=new i(a);return await e.pack(t,s),e}async function o(t,i={}){return await n(t.getImageData(0,0,t.canvas.width,t.canvas.height),t.canvas.width,i)}async function r(t,i={}){const s=t.getContext("2d");return await o(s,i)}async function h(t=200,i="black",s={}){return await l(t,t,i,s)}async function c(t=100,i="black",s={}){const a=document.createElement("canvas");a.width=2*t,a.height=2*t;const e=a.getContext("2d");return e.fillStyle=i,e.beginPath(),e.arc(t,t,t,0,2*Math.PI),e.closePath(),e.fill(),await o(e,s)}async function l(t=200,i=200,s="black",a={}){const e=document.createElement("canvas");e.width=t,e.height=i;const n=e.getContext("2d");return n.fillStyle=s,n.fillRect(0,0,t,i),await o(n,a)}export{i as CirclePacker,s as fromBlob,r as fromCanvas,c as fromCircle,o as fromContext2D,e as fromImage,n as fromImageData,l as fromRect,h as fromSquare,a as fromURL};