memoscan
← all memos

Memo 0x2d8d2eba…cddb0a on Ethereum

(x1<=x0)continue; const colt=new Float64Array(S); let any=false; for(let y=0;y<S;y++){let a=0,n=0; for(let x=x0;x<x1;x++) if(F.mask[y*S+x]){a+=t[y*S+x];n++;} colt[y]=n?a/n:0; if(n)any=true;} if(!any)continue; let y0=-1,y1=-1; for(let y=0;y<S;y++){let has=false; for(let x=x0;x<x1;x++) if(F.mask[y*S+x]){has=true;break;} if(has){if(y0<0)y0=y;y1=y;}} let y=y0; while(y<y1){const v=colt[y],L=Math.round(2+14*v),gap=Math.round(1+7*(1-v)); for(let q=y;q<Math.min(y+L,S);q++)for(let x=x0;x<x1;x++) if(F.mask[q*S+x])o[q*S+x]=1; y+=L+Math.max(gap,1);}} return o;}; /* ---------- tiling / cellular ---------- */ A.truchet=(F,p)=>{const S=F.size,t=tone(F,Object.assign({gamma:0.9},p)); const c=p.cell||9,{tb,den}=cellMean(F,t,c),o=new Float64Array(S*S); for(let gy=0;gy+c<=S;gy+=c)for(let gx=0;gx+c<=S;gx+=c){ const ci=(gy+(c>>1))*S+gx+(c>>1); if(den[ci]<0.5)continue; const v=tb[ci]; if(v<0.10)continue; const flip=(((gx/c)*7919+(gy/c)*104729)%2)===0, w=v>0.55?1.9:1.0; for(let y=0;y<c;y++)for(let x=0;x<c;x++){ const a=flip? (Math.abs(Math.hypot(x,y)-c/2)<w)||(Math.abs(Math.hypot(x-c,y-c)-c/2)<w) : (Math.abs(Math.hypot(x-c,y)-c/2)<w)||(Math.abs(Math.hypot(x,y-c)-c/2)<w); if(a&&F.mask[(gy+y)*S+gx+x])o[(gy+y)*S+gx+x]=1;}} return o;}; A.maze=(F,p)=>{const S=F.size,t=tone(F,Object.assign({gamma:0.85},p)); const c=p.cell||7,{tb,den}=cellMean(F,t,c),o=new Float64Array(S*S),r=rng32(p.seed||1); for(let gy=0;gy+c<S;gy+=c)for(let gx=0;gx+c<S;gx+=c){ const ci=(gy+(c>>1))*S+gx+(c>>1); if(den[ci]<0.5)continue; const v=tb[ci]; if(v<0.06)continue; const pr=0.25+0.75*v; if(r()<pr) for(let y=0;y<c;y++) if(F.mask[(gy+y)*S+gx])o[(gy+y)*S+gx]=1; if(r()<pr) for(let x=0;x<c;x++) if(F.mask[gy*S+gx+x])o[gy*S+gx+x]=1; if(v>0.62&&r()<v) for(let y=0;y<c;y++){const xx=gx+c-1; if(xx<S&&F.mask[(gy+y)*S+xx])o[(gy+y)*S+xx]=1;}} return o;}; A.brick=(F,p)=>{const S=F.size,t=tone(F,Object.assign({gamma:0.9},p)); const cw=p.cw||14,ch=p.chh||7,{tb,den}=cellMean(F,t,cw),o=new Float64Array(S*S); for(let y=0;y<S;y++)for(let x=0;x<S;x++){const i=y*S+x;if(!F.mask[i]||den[i]<=0.5)continue; const row=Math.floor(y/ch),xo=(row%2===0)?0:cw/2; const u=wrap((x+xo)/cw),v=wrap(y/ch),ins=0.5*(1-cl(tb[i],0,1)); if(u>ins&&u<1-ins&&v>0.14&&v<0.86)o[i]=1;} return o;}; A.checker=(F,p)=>{const S=F.size,t=tone(F,Object.assign({gamma:0.75,white:0.04},p)); const c=p.cell||7,{tb,den}=cellMean(F,t,c),o=new Float64Array(S*S); for(let y=0;y<S;y++)for(let x=0;x<S;x++){const i=y*S+x;if(!F.mask[i]||den[i]<=0.5)continue; const par=((Math.floor(x/c)+Math.floor(y/c))%2)===0; const s=cl(tb[i]*1.5,0,1),u=Math.abs(wrap(x/c)-0.5)*2,v=Math.abs(wrap(y/c)-0.5)*2,r=Math.max(u,v); if((par&&r<s)||(!par&&r<cl((tb[i]-0.5)*3,0,1)))o[i]=1;} return o;}; A.crossgrid=(F,p)=>{const S=F.size,t=tone(F,Object.assign({gamma:0.70,white:0.04},p)); const c=p.cell||8,{tb,den}=cellMean(F,t,c),o=new Float64Array(S*S); for(let y=0;y<S;y++)for(let x=0;x<S;x++){const i=y*S+x;if(!F.mask[i]||den[i]<=0.5)continue; const u=Math.abs((x%c)-c/2),v=Math.abs((y%c)-c/2); const L=0.5*c*cl(tb[i]*1.5,0,1),arm=0.5+1.4*cl(tb[i],0,1); if((u<arm&&v<L)||(v<arm&&u<L))o[i]=1;} return o;}; A.trigrid=(F,p)=>{const S=F.size,t=tone(F,Object.assign({gamma:0.9},p)); const c=p.cell||11,{tb,den}=cellMean(F,t,c|0),o=new Float64Array(S*S); for(let y=0;y<S;y++)for(let x=0;x<S;x++){const i=y*S+x;if(!F.mask[i]||den[i]<=0.5)continue; const row=Math.floor(y/c),u=wrap((x+(row%2===0?0:c/2))/c),v=wrap(y/c); if(Math.min(Math.min(u,1-u),v)<0.42*cl(tb[i],0,1))o[i]=1;} return o;}; A.weave=(F,p)=>{const S=F.size,t=tone(F,Object.assign({gamma:0.85},p)); const c=p.cell||11,{tb,den}=cellMean(F,t,c),o=new Float64Array(S*S); for(let y=0;y<S;y++)for(let x=0;x<S;x++){const i=y*S+x;if(!F.mask[i]||den[i]<=0.5)continue; const h=((Math.floor(x/c)+Math.floor(y/c))%2)===0; /* no 1.3 gain: it pushed mid-tones straight to the clamp, so strand width saturated and the lit side of the head went flat white. */ const u=wrap(x/c),v=wrap(y/c),w=0.5*cl(tb[i],0,1); if(h?Math.abs(v-0.5)<w:Math.abs(u-0.5)<w)o[i]=1;} return o;}; A.herringbone=(F,p)=>{const S=F.size,t=tone(F,Object.assign({gamma:0.85},p)); const c=p.cell||13,{tb,den}=cellMean(F,t,c),o=new Float64Array(S*S); const a1=Math.PI/4,a2=-Math.PI/4; for(let y=0;y<S;y++)for(let x=0;x<S;x++){const i=y*S+x;if(!F.mask[i]||den[i]<=0.5)continue; const blk=((Math.floor(x/c)+Math.floor(y/c))%2)===0; const q=blk?(x*Math.cos(a1)+y*Math.sin(a1))/3.2:(x*Math.cos(a2)+y*Math.sin(a2))/3.2; if(Math.abs(wrap(q)-0.5)<cl(tb[i]*0.62,0,0.5))o[i]=1;} return o;}; A.scales=(F,p)=>{const S=F.size,t=tone(F,Object.assign({gamma:0.85},p)); const c=p.cell||13,{tb,den}=cellMean(F,t,c|0),o=new Float64Array(S*S); for(let y=0;y<S;y++)for(let x=0;x<S;x++){const i=y*S+x;if(!F.mask[i]||den[i]<=0.5)continue; const row=Math.floor(y/(c*0.5)),u=((x+(row%2===0?0:c/2))%c)-c/2,v=y%(c*0.5); const w=0.9+1.9*cl(tb[i],0,1); if(Math.abs(Math.hypot(u,v)-c*0.5)<w)o[i]=1;} return o;}; A.nested=(F,p)=>{const S=F.size,t=tone(F,Object.assign({gamma:0.72,white:0.04},p)); const c=p.cell||11,{tb,den}=cellMean(F,t,c),o=new Float64Array(S*S); for(let y=0;y<S;y++)for(let x=0;x<S;x++){const i=y*S+x;if(!F.mask[i]||den[i]<=0.5)continue; const u=Math.abs(wrap(x/c)-0.5)*2,v=Math.abs(wrap(y/c)-0.5)*2,r=Math.max(u,v); /* lv may reach 0 so dark cells draw NOTHING. `floor(g*4)+1` with a min of 1 meant every cell in the mask drew at least one ring regardless of tone, which is why tone could never empty the shadow side. */ const g=cl(tb[i]*1.45,0,1),lv=cl(Math.round(g*4),0,4); for(let k=1;k<=4;k++) if(lv>=k&&Math.abs(r-k/4)<0.13){o[i]=1;break;}} return o;}; A.argyle=(F,p)=>{const S=F.size,t=tone(F,Object.assign({gamma:0.85},p)); const c=p.cell||15,{tb,den}=cellMean(F,t,c),o=new Float64Array(S*S); for(let y=0;y<S;y++)for(let x=0;x<S;x++){const i=y*S+x;if(!F.mask[i]||den[i]<=0.5)continue; const a=wrap((x+y)/c),b=wrap((x-y)/c),w=cl(tb[i]*0.55,0.02,0.5); if(Math.abs(a-0.5)<w||Math.abs(b-0.5)<w)o[i]=1;} return o;}; A.tally=(F,p)=>{const S=F.size,t=tone(F,Object.assign({gamma:0.70,white:0.04},p)); const c=p.cell||9,{tb,den}=cellMean(F,t,c),o=new Float64Array(S*S); for(let y=0;y<S;y++)for(let x=0;x<S;x++){const i=y*S+x;if(!F.mask[i]||den[i]<=0.5)continue; const u=wrap(x/c),v=wrap(y/c),g=cl(tb[i]*1.5,0,1),cnt=cl(Math.floor(g*5),0,5); let hit=false; for(let k=0;k<4;k++){const pos=(k+0.6)/4.6; if(Math.abs(u-pos)<0.10&&v>0.12&&v<0.88&&cnt>k){hit=true;break;}} if(!hit&&cnt>=5&&Math.abs((u-0.5)*1.2+(v-0.5))<0.10&&v>0.12&&v<0.88)hit=true; if(hit)o[i]=1;} return o;}; A.quadtree=(F,p)=>{const S=F.size,t=tone(F,Object.assign({gamma:0.9},p)),o=new Float64Array(S*S); const minc=p.minc||4,maxd=p.maxd||6,thr=p.thresh||0.055; function rec(y,x,s,d){ if(y>=S||x>=S)return; let n=0,sum=0,sq=0; for(let yy=y;yy<Math.min(y+s,S);yy++)for(let xx=x;xx<Math.min(x+s,S);xx++){ const i=yy*S+xx; if(F.mask[i]){n++;sum+=t[i];sq+=t[i]*t[i];}} if(n===0)return; const mean=sum/n,sd=Math.sqrt(Math.max(sq/n-mean*mean,0)); if(s<minc||d>=maxd||sd<thr||s<=minc*2){ for(let yy=y;yy<Math.min(y+s,S);yy++) if(F.mask[yy*S+x])o[yy*S+x]=1; for(let xx=x;xx<Math.min(x+s,S);xx++) if(F.mask[y*S+xx])o[y*S+xx]=1; return;} const h=s>>1; rec(y,x,h,d+1);rec(y,x+h,h,d+1);rec(y+h,x,h,d+1);rec(y+h,x+h,h,d+1);} let P2=1; while(P2<S)P2*=2; rec(0,0,P2>>1,0);rec(0,P2>>1,P2>>1,0);rec(P2>>1,0,P2>>1,0);rec(P2>>1,P2>>1,P2>>1,0); return o;}; A.wolfram=(F,p)=>{const S=F.size,t=tone(F,Object.assign({gamma:0.9},p)); const c=p.cell||3,rule=p.rule||110,gw=Math.floor(S/c),gh=Math.floor(S/c); const r=rng32(p.seed||1); let row=new Uint8Array(gw); for(let i=0;i<gw;i++) row[i]=r()<0.5?1:0; const R=[]; for(let i=0;i<8;i++) R.push((rule>>i)&1); const grid=[]; for(let y=0;y<gh;y++){ grid.push(row.slice()); const nr=new Uint8Array(gw); for(let i=0;i<gw;i++){const l=row[(i-1+gw)%gw],m=row[i],rr=row[(i+1)%gw]; nr[i]=R[(l<<2)|(m<<1)|rr];} row=nr;} const {tb,den}=cellMean(F,t,c*3),o=new Float64Array(S*S); for(let y=0;y<S;y++)for(let x=0;x<S;x++){const i=y*S+x;if(!F.mask[i]||tb[i]<=0.20)continue; const gy=Math.floor(y/c),gx=Math.floor(x/c); if(gy<gh&&gx<gw&&grid[gy][gx])o[i]=1;} return o;}; A.lowpoly=(F,p)=>{const S=F.size,t=tone(F,Object.assign({gamma:0.9},p)); /* Delaunay-free: connect each site to its nearest neighbours (relative neighbourhood graph approximation) — visually equivalent wireframe. */ const pts=pickWeighted(F.mask,t,S,p.sites||520,p.seed||1); const er=[]; for(let y=1;y<S-1;y++)for(let x=1;x<S-1;x++){const i=y*S+x; if(F.mask[i]&&(!F.mask[i-1]||!F.mask[i+1]||!F.mask[i-S]||!F.mask[i+S]))er.push(i);} const step=Math.max((er.length/220)|0,1); for(let k=0;k<er.length;k+=step) pts.push(er[k]); const P=pts.map(i=>[i%S,(i/S)|0]); const o=new Float64Array(S*S); for(let a=0;a<P.length;a++){ const d=[]; for(let b=0;b<P.length;b++){ if(a===b)continue; d.push([Math.hypot(P[a][0]-P[b][0],P[a][1]-P[b][1]),b]);} d.sort((u,v)=>u[0]-v[0]); for(let k=0;k<Math.min(4,d.length);k++){const b=d[k][1]; if(b<a)continue; line(o,F.mask,S,P[a][0],P[a][1],P[b][0],P[b][1]);}} return o;}; A.lissajous=(F,p)=>{const S=F.size,t=tone(F,Object.assign({gamma:0.9},p)),o=new Float64Array(S*S); const [cx,cy]=centroid(F.mask,S),r=rng32(p.seed||1),curves=p.curves||150; for(let k=0;k<curves;k++){ const A1=(0.18+r()*0.32)*S,B1=(0.18+r()*0.32)*S; const aa=1+((r()*6)|0),bb=1+((r()*6)|0),ph=r()*2*Math.PI,thr=0.12+r()*0.43; for(let q=0;q<1400;q++){const T=q/1400*2*Math.PI; const x=Math.round(cx+A1*Math.sin(aa*T+ph)),y=Math.round(cy+B1*Math.sin(bb*T)); if(x<0||x>=S||y<0||y>=S)continue; const i=y*S+x; if(F.mask[i]&&t[i]>thr)o[i]=1;}} return o;}; A.ascii=(F,p)=>{ const S=F.size,t=tone(F,Object.assign({gamma:0.95},p)); /* real bitmap typography: a 5x7 font, a density-sorted character ramp, and per-cell glyph choice. mark scale sets the type size. */ const ms=p.ms||1; const cw=Math.max(Math.round(5*ms+1),4), ch=Math.max(Math.round(7*ms+1),6); const sx=(cw-1)/5, sy=(ch-1)/7; // glyph -> cell scale const ramp=GLYPH_RAMP(p.ramp||'classic'); const n=ramp.length; const {tb,den}=cellMean(F,t,Math.max(cw,ch)); const o=new Float64Array(S*S); for(let gy=0;gy+ch<=S;gy+=ch)for(let gx=0;gx+cw<=S;gx+=cw){ const ci=(gy+(ch>>1))*S+gx+(cw>>1); if(den[ci]<0.45)continue; const v=cl(tb[ci],0,1); const gi=cl(Math.round(v*(n-1)),0,n-1); const bits=GLYPH_FONT[ramp[gi]]; if(!bits)continue; for(let r=0;r<7;r++){ const row=bits[r]; if(!row)continue; for(let c=0;c<5;c++){ if(!(row & (1<<(4-c))))continue; /* paint the glyph pixel scaled into the cell */ const y0=gy+Math.round(r*sy), x0=gx+Math.round(c*sx); const y1=gy+Math.round((r+1)*sy), x1=gx+Math.round((c+1)*sx); for(let y=y0;y<Math.max(y1,y0+1);y++)for(let x=x0;x<Math.max(x1,x0+1);x++){ if(y<0||y>=S||x<0||x>=S)continue; const i=y*S+x; if(F.mask[i])o[i]=1; } } } } return o;}; A.voiceprint=(F,p)=>{const S=F.size,t=tone(F,Object.assign({gamma:0.9},p)),o=new Float64Array(S*S); const rows=p.rows||60,rh=Math.max(S/rows,2); const {tb,den}=cellMean(F,t,Math.max(Math.round(rh),2)); for(let y=0;y<S;y++)for(let x=0;x<S;x++){const i=y*S+x;if(!F.mask[i])continue; const frac=(y%rh)/rh,half=cl(tb[i],0,1)*0.5; if(Math.abs(frac-0.5)<half)o[i]=1;} return o;}; A.guilloche=(F,p)=>{const S=F.size,t=tone(F,Object.assign({gamma:0.9},p)),o=new Float64Array(S*S); const [cx,cy]=centroid(F.mask,S); let cnt=0; for(let i=0;i<S*S;i++) if(F.mask[i])cnt++; const R=Math.max(Math.sqrt(cnt),1),rings=p.rings||30,pet=p.petals||20,amp=p.amp||0.045; for(let y=0;y<S;y++)for(let x=0;x<S;x++){const i=y*S+x;if(!F.mask[i])continue; const X=(x-cx)/R,Y=(y-cy)/R,th=Math.atan2(Y,X),rr=Math.hypot(X,Y); const band=(rr+amp*Math.sin(pet*th))*rings; if(Math.abs(wrap(band)-0.5)<0.06+0.30*t[i])o[i]=1;} return o;}; /* helpers */ function pickWeighted(mask,t,S,n,seed){ const idx=[]; for(let i=0;i<S*S;i++) if(mask[i])idx.push(i); const w=idx.map(i=>t[i]+0.06); let tot=0; for(const v of w)tot+=v; const r=rng32(seed),out=[],used=new Set(); const take=Math.min(n,idx.length); let guard=0; while(out.length<take&&guard++<take*40){ let q=r()*tot,k=0; while(k<w.length-1&&q>w[k]){q-=w[k];k++;} if(used.has(k))continue; used.add(k); out.push(idx[k]);} return out; } function line(o,mask,S,x0,y0,x1,y1){ const dx=Math.abs(x1-x0),dy=Math.abs(y1-y0); const sx=x0<x1?1:-1,sy=y0<y1?1:-1; let err=dx-dy,x=x0,y=y0; for(let g=0;g<4000;g++){ if(x>=0&&x<S&&y>=0&&y<S&&mask[y*S+x])o[y*S+x]=1; if(x===x1&&y===y1)break; const e2=2*err; if(e2>-dy){err-=dy;x+=sx;} if(e2<dx){err+=dx;y+=sy;}} } })(); /* --- algos4.js --- */ (function(){ /* KYS algorithms part 4 — flow field, circle packing, recursive subdivision. */ 'use strict'; const {A,shade,tone,blur,blueNoise,whiteNoise,rng32,centroid,edt}=KYSU; const {cellMean,pickN,smoothDepth,wrap,cl}=KYSU; /* ---- 81. FLOW FIELD ------------------------------------------------- * Long advected streamlines. The field is a stable global angle rotated by * smoothed depth — NOT raw gradient direction, which is undefined on flat * regions and produced whorls in an earlier attempt. */ A.flowfield=(F,p)=>{ const S=F.size, t=tone(F,Object.assign({gamma:0.9},p)), o=new Float64Array(S*S); const ms=p.ms||1; const dd=blur((()=>{const d=new Float64Array(S*S); for(let i=0;i<S*S;i++) d[i]=F.mask[i]?F.depth[i]:0; return d;})(), S, 6); const base=(p.base===undefined?-28:p.base)*Math.PI/180; const swirl=(p.swirl===undefined?2.6:p.swirl); /* sub-pixel step + continuous stamping: the previous version wrote one pixel per unit step, which staircased. Now we walk in small increments and stamp a soft round nib along the path, so strokes read as drawn lines rather than aliased runs. */ const step=0.45; const maxLen=Math.round((p.flowlen||70)*Math.sqrt(ms)); const n=Math.max(Math.round((p.flowseeds||4200)/(ms*ms*1.2)),150); const seeds=pickN(F.mask,S,n,(p.seed||1)); const rad=Math.max(0.55*ms,0.55); // nib radius in px (float) const R=Math.ceil(rad); const stamp=(fx,fy)=>{ const x0=Math.floor(fx-R), x1=Math.ceil(fx+R); const y0=Math.floor(fy-R), y1=Math.ceil(fy+R); for(let y=y0;y<=y1;y++){ if(y<0||y>=S)continue; for(let x=x0;x<=x1;x++){ if(x<0||x>=S)continue; const i=y*S+x; if(!F.mask[i])continue; /* coverage of this pixel by the nib, smoothed at the rim */ const d=Math.hypot(x+0.5-fx, y+0.5-fy); const cov=cl(rad+0.5-d,0,1); if(cov>o[i]) o[i]=cov; } } }; const angAt=(fx,fy)=>{ /* bilinear sample of the depth field so the field itself is smooth */ const x=cl(fx,0,S-1.001), y=cl(fy,0,S-1.001); const xi=x|0, yi=y|0, tx=x-xi, ty=y-yi; const a=dd[yi*S+xi], b=dd[yi*S+xi+1]; const c=dd[(yi+1)*S+xi], e=dd[(yi+1)*S+xi+1]; const v=(a*(1-tx)+b*tx)*(1-ty)+(c*(1-tx)+e*tx)*ty; return base + swirl*(v-0.5)*Math.PI; }; /* separation grid: a streamline dies when it comes too close to one already drawn. Without this, strokes merge into solid slabs at high seed counts. */ const sep=Math.max(rad*2.0+0.9, 1.6); const gs=Math.max(Math.round(sep),1), gw=Math.ceil(S/gs); const occ=new Int32Array(gw*gw).fill(-1); let sid=0; const tooClose=(fx,fy,id)=>{ const gx=(fx/gs)|0, gy=(fy/gs)|0; for(let j=gy-1;j<=gy+1;j++){ if(j<0||j>=gw)continue; for(let i2=gx-1;i2<=gx+1;i2++){ if(i2<0||i2>=gw)continue; const v=occ[j*gw+i2]; if(v>=0 && v!==id) return true; } } return false; }; for(const s of seeds){ let x=(s%S)+0.5, y=((s/S)|0)+0.5; const id=sid++; const px=[],py=[]; const life=Math.round(maxLen*(0.35+0.9*t[s])/step); for(let k=0;k<life;k++){ if(x<0||x>=S-1||y<0||y>=S-1) break; const i=(y|0)*S+(x|0); if(!F.mask[i]) break; if(k>2 && tooClose(x,y,id)) break; px.push(x); py.push(y); const a=angAt(x,y); x+=Math.cos(a)*step; y+=Math.sin(a)*step; } /* discard stubs: a stroke shorter than minLen is an artefact, not a mark */ const minLen=Math.max(Math.round(6/step),8); if(px.length<minLen) continue; for(let q=0;q<px.length;q++){ occ[(((py[q]/gs)|0)*gw)+((px[q]/gs)|0)]=id; stamp(px[q],py[q]); } } /* threshold the coverage field: keeps rim pixels that are >half covered, which is what removes the staircase without needing greyscale output */ for(let i=0;i<S*S;i++) o[i]= o[i]>0.5?1:0; return o; }; /* ---- 82. CIRCLE PACKING -------------------------------------------- * True greedy packing: candidate centres tested against placed circles, * radius limited by tone (small in shadow, large in light). */ A.packing=(F,p)=>{ const S=F.size, t=tone(F,Object.assign({gamma:0.9},p)), o=new Float64Array(S*S); const ms=p.ms||1, r=rng32(p.seed||1); const lw=Math.max(ms*0.9,0.9); /* the STROKE has width, so a circle must fit its outer edge inside the silhouette: R + lw <= dist. Reserving only 0.5px clipped 80% of circles. */ const pad=lw+0.5; const rmin=Math.max(1.2*ms,1), rmax=Math.max(14*ms,rmin+2); const tries=Math.round((p.packtries||9000)); const cx=[],cy=[],cr=[]; const dist=edt(F.mask,S); for(let k=0;k<tries;k++){ const x=(r()*S)|0, y=(r()*S)|0, i=y*S+x; if(!F.mask[i]) continue; let R=rmin+(rmax-rmin)*Math.pow(1-cl(t[i],0,1),1.4); /* shrink to fit rather than clip: the circle packs as close to the edge as it can while staying wholly inside */ R=Math.min(R, dist[i]-pad); if(R<rmin) continue; let ok=true; for(let j=cx.length-1;j>=0;j--){ const dx=cx[j]-x, dy=cy[j]-y; if(dx*dx+dy*dy < (cr[j]+R+1)*(cr[j]+R+1)){ ok=false; break; } } if(!ok) continue; cx.push(x); cy.push(y); cr.push(R); } for(let j=0;j<cx.length;j++){ const R=cr[j], x0=cx[j], y0=cy[j]; const lo=Math.max(0,Math.floor(y0-R-lw)), hi=Math.min(S-1,Math.ceil(y0+R+lw)); for(let y=lo;y<=hi;y++){ const xl=Math.max(0,Math.floor(x0-R-lw)), xr=Math.min(S-1,Math.ceil(x0+R+lw)); for(let x=xl;x<=xr;x++){ const d=Math.hypot(x-x0,y-y0); if(Math.abs(d-R)<lw && F.mask[y*S+x]) o[y*S+x]=1; } } } return o; }; /* ---- 83. RECURSIVE SUBDIVISION (Mondrian) --------------------------- * Split rectangles at random ratios, deeper where tone varies. Cells above a * tone threshold get filled with a dither so the face reads. */ A.subdivide=(F,p)=>{ const S=F.size, t=tone(F,Object.assign({gamma:0.9},p)), o=new Float64Array(S*S); const ms=p.ms||1, r=rng32(p.seed||1); const minSide=Math.max(Math.round(7*ms),4); const maxDepth=p.maxdepth||9; const bn=blueNoise(S,(p.seed||1)+7,1.6); const stats=(x,y,w,h)=>{ let n=0,s=0,s2=0; const sx=Math.max(1,Math.floor(w/12)), sy=Math.max(1,Math.floor(h/12)); for(let j=y;j<y+h;j+=sy)for(let i=x;i<x+w;i+=sx){ if(i>=S||j>=S)continue; const k=j*S+i; if(!F.mask[k])continue; n++; s+=t[k]; s2+=t[k]*t[k]; } if(!n) return null; const m=s/n; return {n,mean:m,sd:Math.sqrt(Math.max(s2/n-m*m,0))}; }; const edge=(x,y,w,h)=>{ for(let i=x;i<x+w;i++){ if(y<S&&F.mask[y*S+i]) o[y*S+i]=1; const yb=y+h-1; if(yb<S&&F.mask[yb*S+i]) o[yb*S+i]=1; } for(let j=y;j<y+h;j++){ if(x<S&&F.mask[j*S+x]) o[j*S+x]=1; const xr=x+w-1; if(xr<S&&F.mask[j*S+xr]) o[j*S+xr]=1; } }; const fill=(x,y,w,h,lvl)=>{ for(let j=y;j<y+h;j++)for(let i=x;i<x+w;i++){ if(i>=S||j>=S)continue; const k=j*S+i; if(F.mask[k] && bn[k]<lvl) o[k]=1; } }; (function rec(x,y,w,h,d){ if(x>=S||y>=S||w<2||h<2) return; const st=stats(x,y,w,h); if(!st) return; const split = d<maxDepth && w>minSide*2 && h>minSide*2 && (st.sd>0.045 || d<2); if(!split){ edge(x,y,w,h); /* tone 0 (lit) -> nearly empty, tone 1 (shadow) -> dense. Cap well below saturation: a fully-inked cell reads as a blown-out white slab and loses the dither texture that makes the method work. */ /* gamma the cell tone so mid/high tones stay textured rather than crowding toward solid; cap keeps the brightest cells readable */ fill(x+1,y+1,Math.max(w-2,0),Math.max(h-2,0), Math.min(Math.pow(cl(st.mean,0,1),1.45)*0.85,0.62)); return; } const ratio=0.32+r()*0.36; // random, not always half if(w>=h){ const cut=Math.max(minSide,Math.round(w*ratio)); rec(x,y,cut,h,d+1); rec(x+cut,y,w-cut,h,d+1); } else { const cut=Math.max(minSide,Math.round(h*ratio)); rec(x,y,w,cut,d+1); rec(x,y+cut,w,h-cut,d+1); } })(0,0,S,S,0); return o; }; })(); /* --- algos5.js --- */ (function(){ /* KYS algorithms part 5 — restored methods. * oil / attractor / stringart, rebuilt with the lessons since. */ 'use strict'; const {A,shade,tone,blur,blueNoise,whiteNoise,rng32,centroid,edt}=KYSU; const {cellMean,pickN,smoothDepth,wrap,cl}=KYSU; /* ---- FINGERPRINT ---------------------------------------------------- * The v2/v3 look: ridge flow following a smoothed orientation field, so the * ridges whorl around features like a real print. The reason it was cut was * that raw-gradient orientation is undefined on flat regions and produced * noise; the fix is a COHERENCE-WEIGHTED blend toward a stable global angle, * plus enough smoothing that the field varies slowly. */ A.oil=(F,p)=>{ const S=F.size, ms=p.ms||1; const t=tone(F,Object.assign({gamma:0.9},p)); const lam=shade(F,p); /* structure tensor of the SHADING, heavily smoothed */ const g=blur(lam,S,3.0); const gx=new Float64Array(S*S), gy=new Float64Array(S*S); for(let y=1;y<S-1;y++)for(let x=1;x<S-1;x++){ const i=y*S+x; gx[i]=(g[i+1]-g[i-1])*0.5; gy[i]=(g[i+S]-g[i-S])*0.5; } const sm=(p.orient===undefined?11:p.orient); const Jxx=blur((()=>{const a=new Float64Array(S*S);for(let i=0;i<S*S;i++)a[i]=gx[i]*gx[i];return a;})(),S,sm); const Jyy=blur((()=>{const a=new Float64Array(S*S);for(let i=0;i<S*S;i++)a[i]=gy[i]*gy[i];return a;})(),S,sm); const Jxy=blur((()=>{const a=new Float64Array(S*S);for(let i=0;i<S*S;i++)a[i]=gx[i]*gy[i];return a;})(),S,sm); const base=(p.base===undefined?78:p.base)*Math.PI/180; const th=new Float64Array(S*S); for(let i=0;i<S*S;i++){ const tr=Jxx[i]+Jyy[i], det=Jxx[i]*Jyy[i]-Jxy[i]*Jxy[i]; const disc=Math.sqrt(Math.max(tr*tr-4*det,0)); const coh=tr>1e-12 ? cl(disc/(tr+1e-12),0,1) : 0; const w=cl(coh*2.2,0,1); // trust the field only where coherent const a=0.5*Math.atan2(2*Jxy[i], Jxx[i]-Jyy[i]); const cx=w*Math.cos(2*a)+(1-w)*Math.cos(2*base); const sy=w*Math.sin(2*a)+(1-w)*Math.sin(2*base); th[i]=0.5*Math.atan2(sy,cx); } /* integrate a phase along the ridge direction, then threshold by tone. Sub-pixel phase (not a per-pixel wave) keeps the ridges smooth. */ const freq=(p.freq===undefined?0.30:p.freq)/Math.max(ms,0.3); const o=new Float64Array(S*S); const dd=blur((()=>{const a=new Float64Array(S*S); for(let i=0;i<S*S;i++)a[i]=F.mask[i]?F.depth[i]:0; return a;})(),S,4); for(let y=0;y<S;y++)for(let x=0;x<S;x++){ const i=y*S+x; if(!F.mask[i])continue; const a=th[i]+Math.PI/2; // ridges run across the gradient const ph=(x*Math.cos(a)+y*Math.sin(a))*freq + 7.0*dd[i]; const v=Math.cos(ph); const thr=0.52-1.05*cl(t[i],0,1); // duty cycle carries tone if(v>thr)o[i]=1; } return o; }; /* ---- STRING ART ----------------------------------------------------- * The original saturated to ~92% ink because each chord subtracted too little * residual and chords were accepted on mean tone alone. Fixes: anti-aliased * accumulation, a hard per-pixel ink budget, and rejecting chords that do not * beat a rising bar. */ })(); root.KYSU=KYSU; root.KYS_A=KYSU.A; })(typeof self!=="undefined"?self:this);