UNPKG

stippled-image

Version:

Custom element to show the stippled version of image

18 lines (17 loc) 8.76 kB
export const workerScript = ` var stipple=function(t){"use strict";const i=Math.pow(2,-52),s=new Uint32Array(512);class n{static from(t,i=c,s=u){const e=t.length,h=new Float64Array(2*e);for(let n=0;n<e;n++){const e=t[n];h[2*n]=i(e),h[2*n+1]=s(e)}return new n(h)}constructor(t){const i=t.length>>1;if(i>0&&"number"!=typeof t[0])throw new Error("Expected coords to contain numbers.");this.coords=t;const s=Math.max(2*i-5,0);this._triangles=new Uint32Array(3*s),this._halfedges=new Int32Array(3*s),this._hashSize=Math.ceil(Math.sqrt(i)),this._hullPrev=new Uint32Array(i),this._hullNext=new Uint32Array(i),this._hullTri=new Uint32Array(i),this._hullHash=new Int32Array(this._hashSize).fill(-1),this._ids=new Uint32Array(i),this._dists=new Float64Array(i),this.update()}update(){const{coords:t,_hullPrev:s,_hullNext:n,_hullTri:h,_hullHash:l}=this,f=t.length>>1;let c=1/0,u=1/0,d=-1/0,_=-1/0;for(let i=0;i<f;i++){const s=t[2*i],n=t[2*i+1];s<c&&(c=s),n<u&&(u=n),s>d&&(d=s),n>_&&(_=n),this._ids[i]=i}const g=(c+d)/2,y=(u+_)/2;let w,A,M,p=1/0;for(let i=0;i<f;i++){const s=e(g,y,t[2*i],t[2*i+1]);s<p&&(w=i,p=s)}const b=t[2*w],k=t[2*w+1];p=1/0;for(let i=0;i<f;i++){if(i===w)continue;const s=e(b,k,t[2*i],t[2*i+1]);s<p&&s>0&&(A=i,p=s)}let x=t[2*A],m=t[2*A+1],S=1/0;for(let i=0;i<f;i++){if(i===w||i===A)continue;const s=a(b,k,x,m,t[2*i],t[2*i+1]);s<S&&(M=i,S=s)}let U=t[2*M],z=t[2*M+1];if(S===1/0){for(let i=0;i<f;i++)this._dists[i]=t[2*i]-t[0]||t[2*i+1]-t[1];o(this._ids,this._dists,0,f-1);const i=new Uint32Array(f);let s=0;for(let t=0,n=-1/0;t<f;t++){const e=this._ids[t];this._dists[e]>n&&(i[s++]=e,n=this._dists[e])}return this.hull=i.subarray(0,s),this.triangles=new Uint32Array(0),void(this.halfedges=new Uint32Array(0))}if(r(b,k,x,m,U,z)){const t=A,i=x,s=m;A=M,x=U,m=z,M=t,U=i,z=s}const I=function(t,i,s,n,e,h){const r=s-t,l=n-i,a=e-t,o=h-i,f=r*r+l*l,c=a*a+o*o,u=.5/(r*o-l*a);return{x:t+(o*f-l*c)*u,y:i+(r*c-a*f)*u}}(b,k,x,m,U,z);this._cx=I.x,this._cy=I.y;for(let i=0;i<f;i++)this._dists[i]=e(t[2*i],t[2*i+1],I.x,I.y);o(this._ids,this._dists,0,f-1),this._hullStart=w;let T=3;n[w]=s[M]=A,n[A]=s[w]=M,n[M]=s[A]=w,h[w]=0,h[A]=1,h[M]=2,l.fill(-1),l[this._hashKey(b,k)]=w,l[this._hashKey(x,m)]=A,l[this._hashKey(U,z)]=M,this.trianglesLen=0,this._addTriangle(w,A,M,-1,-1,-1);for(let e,a,o=0;o<this._ids.length;o++){const f=this._ids[o],c=t[2*f],u=t[2*f+1];if(o>0&&Math.abs(c-e)<=i&&Math.abs(u-a)<=i)continue;if(e=c,a=u,f===w||f===A||f===M)continue;let d=0;for(let t=0,i=this._hashKey(c,u);t<this._hashSize&&(d=l[(i+t)%this._hashSize],-1===d||d===n[d]);t++);d=s[d];let _,g=d;for(;_=n[g],!r(c,u,t[2*g],t[2*g+1],t[2*_],t[2*_+1]);)if(g=_,g===d){g=-1;break}if(-1===g)continue;let y=this._addTriangle(g,f,n[g],-1,-1,h[g]);h[f]=this._legalize(y+2),h[g]=y,T++;let p=n[g];for(;_=n[p],r(c,u,t[2*p],t[2*p+1],t[2*_],t[2*_+1]);)y=this._addTriangle(p,f,_,h[f],-1,h[p]),h[f]=this._legalize(y+2),n[p]=p,T--,p=_;if(g===d)for(;_=s[g],r(c,u,t[2*_],t[2*_+1],t[2*g],t[2*g+1]);)y=this._addTriangle(_,f,g,-1,h[g],h[_]),this._legalize(y+2),h[_]=y,n[g]=g,T--,g=_;this._hullStart=s[f]=g,n[g]=s[p]=f,n[f]=p,l[this._hashKey(c,u)]=f,l[this._hashKey(t[2*g],t[2*g+1])]=g}this.hull=new Uint32Array(T);for(let t=0,i=this._hullStart;t<T;t++)this.hull[t]=i,i=n[i];this.triangles=this._triangles.subarray(0,this.trianglesLen),this.halfedges=this._halfedges.subarray(0,this.trianglesLen)}_hashKey(t,i){return Math.floor(function(t,i){const s=t/(Math.abs(t)+Math.abs(i));return(i>0?3-s:1+s)/4}(t-this._cx,i-this._cy)*this._hashSize)%this._hashSize}_legalize(t){const{_triangles:i,_halfedges:n,coords:e}=this;let h=0,r=0;for(;;){const a=n[t],o=t-t%3;if(r=o+(t+2)%3,-1===a){if(0===h)break;t=s[--h];continue}const f=a-a%3,c=o+(t+1)%3,u=f+(a+2)%3,d=i[r],_=i[t],g=i[c],y=i[u];if(l(e[2*d],e[2*d+1],e[2*_],e[2*_+1],e[2*g],e[2*g+1],e[2*y],e[2*y+1])){i[t]=y,i[a]=d;const e=n[u];if(-1===e){let i=this._hullStart;do{if(this._hullTri[i]===u){this._hullTri[i]=t;break}i=this._hullPrev[i]}while(i!==this._hullStart)}this._link(t,e),this._link(a,n[r]),this._link(r,u);const l=f+(a+1)%3;h<s.length&&(s[h++]=l)}else{if(0===h)break;t=s[--h]}}return r}_link(t,i){this._halfedges[t]=i,-1!==i&&(this._halfedges[i]=t)}_addTriangle(t,i,s,n,e,h){const r=this.trianglesLen;return this._triangles[r]=t,this._triangles[r+1]=i,this._triangles[r+2]=s,this._link(r,n),this._link(r+1,e),this._link(r+2,h),this.trianglesLen+=3,r}}function e(t,i,s,n){const e=t-s,h=i-n;return e*e+h*h}function h(t,i,s,n,e,h){const r=(n-i)*(e-t),l=(s-t)*(h-i);return Math.abs(r-l)>=33306690738754716e-32*Math.abs(r+l)?r-l:0}function r(t,i,s,n,e,r){return(h(e,r,t,i,s,n)||h(t,i,s,n,e,r)||h(s,n,e,r,t,i))<0}function l(t,i,s,n,e,h,r,l){const a=t-r,o=i-l,f=s-r,c=n-l,u=e-r,d=h-l,_=f*f+c*c,g=u*u+d*d;return a*(c*g-_*d)-o*(f*g-_*u)+(a*a+o*o)*(f*d-c*u)<0}function a(t,i,s,n,e,h){const r=s-t,l=n-i,a=e-t,o=h-i,f=r*r+l*l,c=a*a+o*o,u=.5/(r*o-l*a),d=(o*f-l*c)*u,_=(r*c-a*f)*u;return d*d+_*_}function o(t,i,s,n){if(n-s<=20)for(let e=s+1;e<=n;e++){const n=t[e],h=i[n];let r=e-1;for(;r>=s&&i[t[r]]>h;)t[r+1]=t[r--];t[r+1]=n}else{let e=s+1,h=n;f(t,s+n>>1,e),i[t[s]]>i[t[n]]&&f(t,s,n),i[t[e]]>i[t[n]]&&f(t,e,n),i[t[s]]>i[t[e]]&&f(t,s,e);const r=t[e],l=i[r];for(;;){do{e++}while(i[t[e]]<l);do{h--}while(i[t[h]]>l);if(h<e)break;f(t,e,h)}t[s+1]=t[h],t[h]=r,n-e+1>=h-s?(o(t,i,e,n),o(t,i,s,h-1)):(o(t,i,s,h-1),o(t,i,e,n))}}function f(t,i,s){const n=t[i];t[i]=t[s],t[s]=n}function c(t){return t[0]}function u(t){return t[1]}function d(t,i,s){return[t+Math.sin(t+i)*s,i+Math.cos(t-i)*s]}class _{constructor(t,[i,s,e,h]){if(!((e=+e)>=(i=+i)&&(h=+h)>=(s=+s)))throw new Error("invalid bounds");this.points=t,this.delaunator=new n(t),this.inedges=new Int32Array(t.length/2),this.hullIndex=new Int32Array(t.length/2),this.init()}update(){this.delaunator.update(),this.init()}find(t,i,s=0){if((t=+t)!=t||(i=+i)!=i)return-1;const n=s;let e;for(;(e=this.step(s,t,i))>=0&&e!==s&&e!==n;)s=e;return e}init(){const t=this.delaunator,i=this.points;if(t.hull&&t.hull.length>2&&function(t){const{triangles:i,coords:s}=t;for(let t=0;t<i.length;t+=3){const n=2*i[t],e=2*i[t+1],h=2*i[t+2];if((s[h]-s[n])*(s[e+1]-s[n+1])-(s[e]-s[n])*(s[h+1]-s[n+1])>1e-10)return!1}return!0}(t)){this.collinear=Int32Array.from({length:i.length/2},(t,i)=>i).sort((t,s)=>i[2*t]-i[2*s]||i[2*t+1]-i[2*s+1]);const t=this.collinear[0],s=this.collinear[this.collinear.length-1],e=[i[2*t],i[2*t+1],i[2*s],i[2*s+1]],h=1e-8*Math.sqrt((e[3]-e[1])**2+(e[2]-e[0])**2);for(let t=0,s=i.length/2;t<s;++t){const s=d(i[2*t],i[2*t+1],h);i[2*t]=s[0],i[2*t+1]=s[1]}this.delaunator=new n(i)}else delete this.collinear;const s=this.halfedges=this.delaunator.halfedges,e=this.hull=this.delaunator.hull,h=this.triangles=this.delaunator.triangles,r=this.inedges.fill(-1),l=this.hullIndex.fill(-1);for(let t=0,i=s.length;t<i;++t){const i=h[t%3==2?t-2:t+1];-1!==s[t]&&-1!==r[i]||(r[i]=t)}for(let t=0,i=e.length;t<i;++t)l[e[t]]=t;e.length<=2&&e.length>0&&(this.triangles=new Int32Array(3).fill(-1),this.halfedges=new Int32Array(3).fill(-1),this.triangles[0]=e[0],this.triangles[1]=e[1],this.triangles[2]=e[1],r[e[0]]=1,2===e.length&&(r[e[1]]=0))}step(t,i,s){const{inedges:n,hullIndex:e,points:h}=this,r=this.triangles,l=this.halfedges,a=this.hull;if(-1===n[t]||!h.length)return(t+1)%(h.length>>1);let o=t,f=(i-h[2*t])**2+(s-h[2*t+1])**2;const c=n[t];let u=c;do{const n=r[u],c=(i-h[2*n])**2+(s-h[2*n+1])**2;if(c<f&&(f=c,o=n),u=u%3==2?u-2:u+1,r[u]!==t)break;if(u=l[u],-1===u){if(u=a[(e[t]+1)%a.length],u!==n&&(i-h[2*u])**2+(s-h[2*u+1])**2<f)return u;break}}while(u!==c);return o}}function g(t,i){const s=4*t;return 1-(.299*i[s]+.587*i[s+1]+.114*i[s+2])/254}return t.stipple=function(t,i,s,n,e,h){const r=n||i*s/50;e=e||[80];const l=Math.max(...e),a=new Float32Array(2*r),o=new Uint8ClampedArray(t),f=new Float64Array(2*r),c=new Float64Array(r);for(let t=0;t<r;++t)for(let n=0;n<60;++n){const n=a[2*t]=Math.floor(Math.random()*i),e=a[2*t+1]=Math.floor(Math.random()*s);if(Math.random()<g(e*i+n,o))break}const u=new _(a,[0,0,i,s]);for(let t=0;t<=l;++t){f.fill(0),c.fill(0);for(let t=0,n=0;t<s;++t)for(let s=0;s<i;++s){const e=g(t*i+s,o);n=u.find(s+.5,t+.5,n),c[n]+=e,f[2*n]+=e*(s+.5),f[2*n+1]+=e*(t+.5)}const n=10*Math.pow(t+1,-.8);for(let t=0;t<r;++t){const i=a[2*t],s=a[2*t+1],e=c[t]?f[2*t]/c[t]:i,h=c[t]?f[2*t+1]/c[t]:s;a[2*t]=i+1.8*(e-i)+(Math.random()-.5)*n,a[2*t+1]=s+1.8*(h-s)+(Math.random()-.5)*n}u.update(),h&&e.indexOf(t)>=0&&h({points:a,width:i,height:s,iteration:t})}return{points:a,width:i,height:s,iteration:l}},t}({}); self.addEventListener('message', (event) => { const {buffer, width, height, pointCount} = event.data; const iterations = [2, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50]; stipple.stipple( buffer, width, height, pointCount, iterations, (info) => { self.postMessage(info); } ); }); `;