@rr0/ufoathome 0.142.0 → 0.143.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.
- package/dist-embed-scene/rr0-scene.mjs +4 -4
- package/dist-embed-sighting/rr0-sighting.mjs +6 -6
- package/dist-embed-sighting-editor/GhostSearchView-B5XvBQYl.js +1 -0
- package/dist-embed-sighting-editor/rr0-sighting-editor.mjs +8 -8
- package/package.json +1 -1
- package/dist-embed-sighting-editor/GhostSearchView-BiyITIC7.js +0 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rr0/ufoathome",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.143.0",
|
|
5
5
|
"description": "UFO@home — record and replay a UFO sighting's shape, movement and appearance",
|
|
6
6
|
"author": "Jérôme Beau <rr0@rr0.org> (https://rr0.org)",
|
|
7
7
|
"license": "MIT",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{P as w,a as W}from"./PictureFetch-X3UpM6JV.js";class T{static SIZE=360;static LIGHT=230;static DARK=80;static STANDS_OUT=12;static GROUND=.025;static WHOLE_GROUND=.08;static GROWN_DRIFT=.02;static INSIDE_WEIGHT=.4;static LINE_LENGTH=12;static LINE_FILL=.2;static STANDING_FULLY=60;static NEAREST=.08;static REACH=.08;static RING=[.04,.08];static SHARE_OF_PEAK=.15;static MAX_PAIRS=6e6;static find(c,o){const a=Math.min(1,T.SIZE/Math.max(c.width,c.height)),s=Math.max(1,Math.round(c.width*a)),l=Math.max(1,Math.round(c.height*a)),r=T.luminanceOf(c,s,l),t=T.blurred(r,s,l,Math.max(4,Math.round(Math.hypot(s,l)*T.GROUND))),e=[],i=[];for(let d=0;d<l;d++)for(let I=0;I<s;I++){const L=d*s+I,M=r[L];M>=T.LIGHT?e.push({x:I,y:d,weight:(M-T.LIGHT+5)/30}):t[L]<T.DARK&&M-t[L]>T.STANDS_OUT&&i.push({x:I,y:d,weight:Math.min(1,(M-t[L])/T.STANDING_FULLY)})}if(i.splice(0,i.length,...T.withoutLines(i,s,l)),e.splice(0,e.length,...T.withoutLines(e,s,l)),i.push(...T.insides(i,e,s,l)),e.length===0||i.length===0)return;const n=T.NEAREST*Math.hypot(s,l),f=T.peak(T.vote(e,i,s,l,n),s,l);if(f.value<=0||f.sharpness<o)return;const u={x:f.x/2,y:f.y/2},{pairs:y,matched:h}=T.pairsAt(u,e,i,r,t,s,l,n);if(y.length===0)return;const p=1/a,m=d=>({...d,x:(d.x+.5)*p-.5,y:(d.y+.5)*p-.5,radius:d.radius*p});return{centre:{x:(u.x+.5)*p-.5,y:(u.y+.5)*p-.5},pairs:y.map(d=>({ghost:m(d.ghost),light:m(d.light),residualPx:d.residualPx*p})),sharpness:f.sharpness,matched:h}}static vote(c,o,a,s,l){const r=new Float32Array(2*a*2*s),t=Math.max(1,Math.ceil(Math.sqrt(c.length*o.length/T.MAX_PAIRS))),e=l*l;for(let i=0;i<c.length;i+=t){const n=c[i];for(let f=0;f<o.length;f+=t){const u=o[f],y=u.x-n.x,h=u.y-n.y;y*y+h*h<e||(r[(n.y+u.y)*2*a+n.x+u.x]+=n.weight*u.weight)}}return r}static peak(c,o,a){const s=2*o,l=2*a,r=T.blurred(c,s,l,3);let t={x:0,y:0,value:0,sharpness:0};const e=T.REACH*Math.hypot(s,l);for(let y=Math.max(0,Math.round(l/2-e));y<Math.min(l,Math.round(l/2+e));y++)for(let h=Math.max(0,Math.round(s/2-e));h<Math.min(s,Math.round(s/2+e));h++){if(Math.hypot(h-s/2,y-l/2)>e)continue;const p=r[y*s+h];p>t.value&&(t={x:h,y,value:p,sharpness:0})}const i=Math.hypot(s,l),n=T.RING[0]*i,f=T.RING[1]*i;let u=0;for(let y=Math.max(0,Math.floor(t.y-f));y<Math.min(l,Math.ceil(t.y+f));y++)for(let h=Math.max(0,Math.floor(t.x-f));h<Math.min(s,Math.ceil(t.x+f));h++){const p=Math.hypot(h-t.x,y-t.y);p>n&&p<f&&(u=Math.max(u,r[y*s+h]))}return t.sharpness=u>0?t.value/u:1/0,t}static pairsAt(c,o,a,s,l,r,t,e){const i=new Float32Array(r*t);for(const M of o)i[M.y*r+M.x]=M.weight;const n=M=>{const A=Math.round(2*c.x-M.x),S=Math.round(2*c.y-M.y);let k=0;for(let x=-2;x<=2;x++)for(let O=-2;O<=2;O++){const E=A+O,R=S+x;E>=0&&R>=0&&E<r&&R<t&&(k+=i[R*r+E])}return M.weight*k},f=new Float32Array(r*t),u=new Uint8Array(r*t);for(const M of a){if(Math.hypot(M.x-(2*c.x-M.x),M.y-(2*c.y-M.y))<e)continue;const A=n(M);A<=0||(u[M.y*r+M.x]=1,f[M.y*r+M.x]=A)}let y;const h=()=>y??=T.blurred(s,r,t,Math.max(4,Math.round(Math.hypot(r,t)*T.WHOLE_GROUND))),p=new Uint8Array(r*t),m=[];for(let M=0;M<u.length;M++){if(!u[M]||p[M])continue;const A=[],S=[M];for(p[M]=1;S.length>0;){const R=S.pop();A.push(R);const N=R%r,P=(R-N)/r;for(let b=-2;b<=2;b++)for(let H=-2;H<=2;H++){const C=N+H,D=P+b;if(C<0||D<0||C>=r||D>=t)continue;const v=D*r+C;u[v]&&!p[v]&&(p[v]=1,S.push(v))}}if(A.length<3)continue;const k=A.reduce((R,N)=>R+f[N],0),x=T.spotOf(T.whole(A,s,h(),r,t),s,h(),r),O=o.filter(R=>Math.hypot(R.x-(2*c.x-x.x),R.y-(2*c.y-x.y))<=x.radius+2);if(O.length===0)continue;const E=T.spotOf(O.map(R=>R.y*r+R.x),s,l,r);m.push({pair:{ghost:x,light:E,residualPx:Math.hypot(x.x+E.x-2*c.x,x.y+E.y-2*c.y)},vote:k,size:A.length})}const d=m.reduce((M,A)=>Math.max(M,A.vote),0),I=[];let L=0;for(const{pair:M,size:A}of m.filter(S=>S.vote>=T.SHARE_OF_PEAK*d).sort((S,k)=>k.vote-S.vote))L+=A,!I.some(S=>Math.hypot(S.ghost.x-M.ghost.x,S.ghost.y-M.ghost.y)<Math.max(S.ghost.radius,M.ghost.radius))&&I.push(M);return{pairs:I.slice(0,12),matched:L}}static withoutLines(c,o,a){const s=new Int32Array(o*a).fill(-1);c.forEach((t,e)=>s[t.y*o+t.x]=e);const l=new Uint8Array(c.length),r=[];for(let t=0;t<c.length;t++){if(l[t])continue;const e=[t];l[t]=1;let i=1/0,n=-1/0,f=1/0,u=-1/0;for(let h=0;h<e.length;h++){const{x:p,y:m}=c[e[h]];i=Math.min(i,p),n=Math.max(n,p),f=Math.min(f,m),u=Math.max(u,m);for(let d=-1;d<=1;d++)for(let I=-1;I<=1;I++){const L=p+I,M=m+d;if(L<0||M<0||L>=o||M>=a)continue;const A=s[M*o+L];A>=0&&!l[A]&&(l[A]=1,e.push(A))}}const y=e.length/((n-i+1)*(u-f+1));if(!(e.length>=T.LINE_LENGTH&&y<T.LINE_FILL))for(const h of e)r.push(c[h])}return r}static insides(c,o,a,s){const l=new Uint8Array(a*s);for(const n of c)l[n.y*a+n.x]=1;for(const n of o)l[n.y*a+n.x]=2;const r=new Uint8Array(a*s),t=[],e=n=>{l[n]||r[n]||(r[n]=1,t.push(n))};for(let n=0;n<a;n++)e(n),e((s-1)*a+n);for(let n=0;n<s;n++)e(n*a),e(n*a+a-1);for(;t.length>0;){const n=t.pop(),f=n%a,u=(n-f)/a;f>0&&e(n-1),f<a-1&&e(n+1),u>0&&e(n-a),u<s-1&&e(n+a)}const i=[];for(let n=0;n<l.length;n++)!l[n]&&!r[n]&&i.push({x:n%a,y:Math.floor(n/a),weight:T.INSIDE_WEIGHT});return i}static whole(c,o,a,s,l){const r=4*Math.sqrt(c.length/Math.PI)+.06*Math.hypot(s,l);let t=0,e=0;for(const m of c)t+=m%s,e+=Math.floor(m/s);t/=c.length,e/=c.length;const i=m=>o[m]<T.LIGHT&&o[m]-a[m]>T.STANDS_OUT/2,n=new Uint8Array(s*l),f=c.filter(i);for(const m of f)n[m]=1;const u=[...f];for(;f.length>0;){const m=f.pop(),d=m%s,I=(m-d)/s;for(const[L,M]of[[1,0],[-1,0],[0,1],[0,-1]]){const A=d+L,S=I+M;if(A<0||S<0||A>=s||S>=l)continue;const k=S*s+A;n[k]||!i(k)||Math.hypot(A-t,S-e)>r||(n[k]=1,u.push(k),f.push(k))}}if(u.length<c.length)return c;let y=0,h=0;for(const m of u)y+=m%s,h+=Math.floor(m/s);return Math.hypot(y/u.length-t,h/u.length-e)<=Math.sqrt(c.length/Math.PI)+T.GROWN_DRIFT*Math.hypot(s,l)?u:c}static spotOf(c,o,a,s){let l=0,r=0,t=0,e=0,i=0;for(const f of c){const u=Math.max(1,o[f]-a[f]);l+=u,r+=u*(f%s),t+=u*Math.floor(f/s),e=Math.max(e,o[f]),i+=a[f]}const n=i/c.length;return{x:r/l,y:t/l,peak:e,contrast:e-n,background:n,radius:Math.sqrt(c.length/Math.PI),color:[e,e,e]}}static luminanceOf(c,o,a){const s=new Float32Array(o*a),l=new Float32Array(o*a),r=o/c.width,t=a/c.height;for(let e=0;e<c.height;e++){const i=Math.min(a-1,Math.floor(e*t));for(let n=0;n<c.width;n++){const f=(e*c.width+n)*4,u=i*o+Math.min(o-1,Math.floor(n*r));s[u]+=.2126*c.data[f]+.7152*c.data[f+1]+.0722*c.data[f+2],l[u]++}}for(let e=0;e<s.length;e++)s[e]/=Math.max(1,l[e]);return s}static blurred(c,o,a,s){const l=new Float64Array((o+1)*(a+1));for(let t=0;t<a;t++){let e=0;for(let i=0;i<o;i++)e+=c[t*o+i],l[(t+1)*(o+1)+i+1]=l[t*(o+1)+i+1]+e}const r=new Float32Array(o*a);for(let t=0;t<a;t++){const e=Math.max(0,t-s),i=Math.min(a,t+s+1);for(let n=0;n<o;n++){const f=Math.max(0,n-s),u=Math.min(o,n+s+1),y=l[i*(o+1)+u]-l[e*(o+1)+u]-l[i*(o+1)+f]+l[e*(o+1)+f];r[t*o+n]=y/((u-f)*(i-e))}}return r}}class g{static OTHER_SCALES=[-2,-1.8,-1.6,-1.4,-1.3,-1.2,-1.1,-.9,-.8,-.7,-.6,-.5,-.4,-.3,.3,.4,.5,.6,.7,.8];static DARK=80;static LARGE_LIGHT_CONTRAST=80;static DOMINATED=.4;static GRID_STEP=2;static GRID_REACH=.12;static ABOVE_CHANCE=1.5;static GREAT_LIGHTS=3;static LONE_REACH=.15;static GROUND_ALIKE=40;static EVEN_ALIKE=20;static CHANCE_ALONE=.5;static CROPPED=.04;static GLARE_SIZE=2;static GLARE_REACH=2.5;static SATURATED=250;static LOST_IN_GLARE=40;static OUT_OF_GLARE=2.5;static SPOT_GHOST_CONTRAST=40;static SHARP_MIRROR=1.5;static SHAPE_MATCHED=25;static MAX_SPOTS=2e3;static find(c){const o=g.searched(c);return{...o,pairs:o.pairs.map(a=>({...a,light:g.glaringOver(a.light,o.lights)}))}}static searched(c){const{width:o,height:a}=c,s=Math.hypot(o,a),l=g.spotsOf(c),r=l.reduce((x,O)=>Math.max(x,O.peak),0),t=Math.min(235,.9*r),e=g.linesOfLight(c,t),i=l.filter(x=>!e[Math.round(x.y)*o+Math.round(x.x)]),n=i.filter(x=>x.peak>=t&&(x.radius<=.025*s?x.contrast>=40:x.contrast>=g.LARGE_LIGHT_CONTRAST)),f=n.reduce((x,O)=>Math.max(x,O.radius),0),u=i.filter(x=>n.includes(x)?x.radius<=g.DOMINATED*f&&x.contrast>=g.LARGE_LIGHT_CONTRAST:x.background<g.DARK&&x.peak>=2*x.background),y={width:o,height:a,lights:n,spots:i,pairs:[],confidence:"none"},h=()=>g.mirrored(c,n,i);if(n.length===0||u.length===0)return h()??y;const p=.006*s,m=x=>x.reduce((O,E)=>O+Math.max(0,1-E.residualPx/p)**2,0),d=g.GRID_STEP*p,I=g.GRID_REACH*s,L=[];for(let x=a/2-I;x<=a/2+I;x+=d)for(let O=o/2-I;O<=o/2+I;O+=d)L.push({x:O,y:x});const M=(x,O=u)=>{let E;for(const R of[...g.voted(O,n,x,o,a),...x===-1?L:[]]){const N=g.fit(O,n,R,x,p);if(!N)continue;const P=m(N.pairs);(!E||P>E.score)&&(E={...N,scale:x,score:P})}return E};let A=M(-1);for(const x of g.OTHER_SCALES){const O=M(x),E=A?.pairs.length??0;O&&O.pairs.length>=Math.max(3,1.5*E,E+2)&&O.score>(A?.score??0)&&(A=O)}let S;if(A&&A.pairs.length>=2){const x=A.scale,O=[[-1,1],[1,-1],[-1,-1]].map(([R,N])=>M(x,u.map(P=>({...P,x:R<0?o-P.x:P.x,y:N<0?a-P.y:P.y})))?.score??0),E=O.reduce((R,N)=>R+N,0)/O.length;if(A.score>g.ABOVE_CHANCE*E){const R=Math.hypot(A.centre.x-o/2,A.centre.y-a/2)/s;if(S={width:o,height:a,lights:n,spots:i,pairs:A.pairs,centre:A.centre,scale:A.scale,cropped:R>g.CROPPED,confidence:A.pairs.length>=3?"high":"low"},S.confidence==="high")return S}}const k=[...u,...i.filter(x=>!n.includes(x)&&!u.includes(x)&&x.contrast>=g.LARGE_LIGHT_CONTRAST&&x.peak>=1.5*x.background)];return h()??S??g.loneGhost(i,n,k,o,a,p)??y}static mirrored(c,o,a){const s=T.find(c,g.SHARP_MIRROR);if(!s)return;const{width:l,height:r}=c,t=Math.hypot(l,r),e=g.atAxis(a,o,s.centre,.006*t),i=e.length>=2?[...e,...s.pairs.filter(u=>!e.some(y=>Math.hypot(y.ghost.x-u.ghost.x,y.ghost.y-u.ghost.y)<=u.ghost.radius))]:s.pairs,n=s.centre,f=Math.hypot(n.x-l/2,n.y-r/2)/t;return{width:l,height:r,lights:o,spots:a,pairs:i,centre:n,scale:-1,cropped:f>g.CROPPED,confidence:i.length>=2||s.matched>=g.SHAPE_MATCHED?"high":"low"}}static glaringOver(c,o){let a=c;for(const s of o)s.radius>=g.GLARE_SIZE*c.radius&&s.radius>a.radius&&Math.hypot(s.x-c.x,s.y-c.y)<=g.GLARE_REACH*s.radius&&(a=s);return a}static atAxis(c,o,a,s){const l=[];for(const r of c.filter(t=>t.contrast>=g.SPOT_GHOST_CONTRAST)){const t={x:2*a.x-r.x,y:2*a.y-r.y};let e;for(const i of o){if(!g.canBeGhostOf(r,i))continue;const n=Math.max(0,Math.hypot(i.x-t.x,i.y-t.y)-i.radius);n<=s&&(!e||n<e.beyond)&&(e={light:i,beyond:n})}!e||!g.apart(r,e.light)||l.some(i=>Math.hypot(i.ghost.x-r.x,i.ghost.y-r.y)<Math.max(2,i.ghost.radius,r.radius))||l.push({ghost:r,light:{...e.light,...t},residualPx:e.beyond})}return l.filter(r=>!l.some(t=>t!==r&&t.ghost.radius<r.ghost.radius&&Math.hypot(t.ghost.x-r.ghost.x,t.ghost.y-r.ghost.y)<r.ghost.radius))}static loneGhost(c,o,a,s,l,r){const t=Math.hypot(s,l),e=[...o].sort((h,p)=>p.contrast*p.radius**2-h.contrast*h.radius**2).slice(0,g.GREAT_LIGHTS);let i;for(const h of e){const p={x:s-h.x,y:l-h.y},m=a.filter(d=>g.canBeGhostOf(d,h)&&Math.hypot(d.x-p.x,d.y-p.y)<=g.LONE_REACH*t&&g.apart(d,h));for(const d of m){const I=a.filter(A=>A.contrast>=d.contrast&&g.canBeGhostOf(A,h)&&g.groundsAlike(A,d)).length,L=Math.hypot(d.x-p.x,d.y-p.y)+r,M=I*Math.PI*L**2/(s*l);(!i||M<i.chance)&&(i={pair:{ghost:d,light:h,residualPx:0},chance:M})}}if(!i||i.chance>=g.CHANCE_ALONE)return;const{ghost:n,light:f}=i.pair,u={x:(n.x+f.x)/2,y:(n.y+f.y)/2},y=Math.hypot(u.x-s/2,u.y-l/2)/t;return{width:s,height:l,lights:o,spots:c,pairs:[i.pair],centre:u,scale:-1,cropped:y>g.CROPPED,confidence:"low"}}static groundsAlike(c,o){const a=Math.max(g.EVEN_ALIKE,c.spread??0,o.spread??0);return Math.abs(c.background-o.background)<=g.GROUND_ALIKE&&Math.abs((c.spread??0)-(o.spread??0))<=a/2}static lightCells=new WeakMap;static lightsNear(c,o){let a=g.lightCells.get(c);if(!a||a.cell!==o){const l=new Map;for(const r of c){const t=g.cellKey(Math.floor(r.x/o),Math.floor(r.y/o)),e=l.get(t);e?e.push(r):l.set(t,[r])}a={cell:o,cells:l},g.lightCells.set(c,a)}const{cells:s}=a;return(l,r)=>{const t=Math.floor(l/o),e=Math.floor(r/o),i=[];for(let n=-1;n<=1;n++)for(let f=-1;f<=1;f++){const u=s.get(g.cellKey(t+n,e+f));u&&i.push(...u)}return i}}static cellKey(c,o){return(c+5e4)*1e5+o+5e4}static linesOfLight(c,o){const{width:a,height:s,data:l}=c,r=[];for(let e=0;e<s;e++)for(let i=0;i<a;i++){const n=(e*a+i)*4;.2126*l[n]+.7152*l[n+1]+.0722*l[n+2]>=o&&r.push({x:i,y:e})}const t=new Uint8Array(a*s);for(const e of r)t[e.y*a+e.x]=1;for(const e of T.withoutLines(r,a,s))t[e.y*a+e.x]=0;return t}static apart(c,o){return Math.hypot(c.x-o.x,c.y-o.y)>=Math.max(4*c.radius,g.OUT_OF_GLARE*o.radius)}static canBeGhostOf(c,o){return c!==o&&(c.peak<o.peak-5||c.radius<=g.DOMINATED*o.radius)}static voted(c,o,a,s,l){const r=.01*Math.hypot(s,l),t=new Map;for(const i of c)for(const n of o){if(!g.canBeGhostOf(i,n))continue;const f=(i.x-a*n.x)/(1-a),u=(i.y-a*n.y)/(1-a);if(f<-.25*s||f>1.25*s||u<-.25*l||u>1.25*l||!g.apart(i,n))continue;const y=`${Math.floor(f/r)},${Math.floor(u/r)}`,h=t.get(y)??{count:0,x:0,y:0};h.count++,h.x+=f,h.y+=u,t.set(y,h)}const e=[...t.entries()].map(([i,n])=>{const[f,u]=i.split(",").map(Number);let y=0,h=0,p=0;for(let m=-1;m<=1;m++)for(let d=-1;d<=1;d++){const I=t.get(`${f+m},${u+d}`);I&&(y+=I.count,h+=I.x,p+=I.y)}return{count:y,centre:{x:h/y,y:p/y},own:n.count}});return e.sort((i,n)=>n.count-i.count||n.own-i.own),e.filter(i=>i.count>=1).slice(0,60).map(i=>i.centre)}static fit(c,o,a,s,l){let r=a,t=[];const e=g.lightsNear(o,3*l);for(const n of[3,2,1,1]){const f=[];for(const y of c){const h=r.x+(y.x-r.x)/s,p=r.y+(y.y-r.y)/s;let m,d=1/0;for(const I of e(h,p)){if(!g.canBeGhostOf(y,I))continue;const L=Math.hypot(I.x-h,I.y-p);L<d&&(d=L,m=I)}m&&d<=n*l&&g.apart(y,m)&&f.push({ghost:y,light:m,residualPx:d*Math.abs(s)})}const u=new Map;for(const y of f){const h=u.get(y.light);(!h||y.residualPx<h.residualPx)&&u.set(y.light,y)}if(t=[...u.values()],t.length===0)return;r={x:t.reduce((y,h)=>y+(h.ghost.x-s*h.light.x)/(1-s),0)/t.length,y:t.reduce((y,h)=>y+(h.ghost.y-s*h.light.y)/(1-s),0)/t.length}}for(const n of t)n.residualPx=Math.hypot(n.ghost.x-(r.x+s*(n.light.x-r.x)),n.ghost.y-(r.y+s*(n.light.y-r.y)));const i=Math.sqrt(t.reduce((n,f)=>n+f.residualPx**2,0)/t.length);return{pairs:t,centre:r,error:i}}static spotsOf(c){const{width:o,height:a,data:s}=c,l=new Float32Array(o*a);for(let n=0;n<o*a;n++)l[n]=.2126*s[n*4]+.7152*s[n*4+1]+.0722*s[n*4+2];const r=Math.max(2,Math.round(Math.hypot(o,a)/500)),t=3*r,e=[];for(let n=t;n<a-t;n++)for(let f=t;f<o-t;f++){const u=l[n*o+f];if(u<40)continue;let y=!0;for(let E=-r;E<=r&&y;E++)for(let R=-r;R<=r;R++){const N=l[(n+E)*o+f+R];if(N>u||N===u&&(E<0||E===0&&R<0)){y=!1;break}}if(!y)continue;let h=-1/0,p=0,m=t,d=0;for(const E of[t,2*t,4*t,8*t,16*t]){if(f<E||n<E||f>=o-E||n>=a-E||E>4*t&&(u<g.SATURATED||h>=g.LOST_IN_GLARE))break;const R=[];for(let P=0;P<32;P++){const b=P/32*2*Math.PI;R.push(l[Math.round(n+E*Math.sin(b))*o+Math.round(f+E*Math.cos(b))])}R.sort((P,b)=>P-b);const N=R[16];u-N>h+5&&(h=u-N,p=N,m=E,d=R[24]-R[8])}if(h<20)continue;const I=p+h/2;let L=0,M=0,A=0,S=0,k=0,x=0,O=0;for(let E=-m+1;E<m;E++)for(let R=-m+1;R<m;R++){const N=(n+E)*o+f+R;if(l[N]<I)continue;L++,M+=s[N*4],A+=s[N*4+1],S+=s[N*4+2];const P=l[N]-I+1;k+=P,x+=P*R,O+=P*E}e.push({x:f+x/k,y:n+O/k,peak:u,contrast:h,background:p,radius:Math.sqrt(L/Math.PI),color:[M/L,A/L,S/L],spread:d})}e.sort((n,f)=>f.contrast-n.contrast);const i=[];for(const n of e){if(i.length>=g.MAX_SPOTS)break;i.some(f=>f.peak>=n.peak&&Math.hypot(f.x-n.x,f.y-n.y)<Math.max(f.radius,r))||i.push(n)}return i}}class U{constructor(c,o,a){this.canvas=c,this.status=o,this.texts=a}static ANALYSIS_SIZE=1600;async search(c){this.status.textContent=this.texts.searching,this.canvas.hidden=!0;let o;try{o=await U.load(c)}catch{this.status.textContent="";return}const a=Math.min(1,U.ANALYSIS_SIZE/Math.max(o.naturalWidth,o.naturalHeight)),s=Math.round(o.naturalWidth*a),l=Math.round(o.naturalHeight*a),r=document.createElement("canvas");r.width=s,r.height=l;const t=r.getContext("2d",{willReadFrequently:!0});t.drawImage(o,0,0,s,l);const e=t.getImageData(0,0,s,l);await new Promise(n=>setTimeout(n,0));const i=g.find(e);return this.draw(o,i),this.status.textContent=this.sentence(i),i}sentence(c){const o=String(c.pairs.length);if(c.confidence==="none")return this.texts.none;const a=c.confidence==="low"?this.texts.low.replace("{count}",o):c.scale===-1?this.texts.found.replace("{count}",o):this.texts.foundScaled.replace("{count}",o).replace("{scale}",(c.scale??0).toLocaleString(void 0,{maximumFractionDigits:2}));return c.cropped?`${a} ${this.texts.cropped}`:a}draw(c,o){const a=this.canvas,s=window.devicePixelRatio||1,l=Math.max(200,a.parentElement?.clientWidth??320),r=l/o.width;a.style.width=`${l}px`,a.style.height=`${Math.round(o.height*r)}px`,a.width=Math.round(l*s),a.height=Math.round(o.height*r*s);const t=a.getContext("2d");t.setTransform(s,0,0,s,0,0),t.drawImage(c,0,0,l,o.height*r);const e=f=>[f.x*r,f.y*r],i=(f,u,y,h=[])=>{t.setLineDash(h),t.lineWidth=y+2,t.strokeStyle="rgba(0, 0, 0, 0.6)",f(),t.lineWidth=y,t.strokeStyle=u,f(),t.setLineDash([])},n=(f,u)=>()=>{const[y,h]=e(f);t.beginPath(),t.arc(y,h,u,0,2*Math.PI),t.stroke()};for(const f of o.pairs)i(()=>{const[u,y]=e(f.ghost),[h,p]=e(f.light);t.beginPath(),t.moveTo(u,y),t.lineTo(h,p),t.stroke()},"rgba(255, 255, 255, 0.85)",1,[4,4]),i(n(f.light,6),"#ffffff",1.5),i(n(f.ghost,Math.max(8,f.ghost.radius*r)),"#ffd23f",2);if(o.centre){const[f,u]=e(o.centre);if(i(()=>{t.beginPath(),t.moveTo(f-7,u),t.lineTo(f+7,u),t.moveTo(f,u-7),t.lineTo(f,u+7),t.stroke()},"#ffd23f",2),o.cropped){const[y,h]=e({x:o.width/2,y:o.height/2});i(()=>{t.beginPath(),t.moveTo(y-5,h-5),t.lineTo(y+5,h+5),t.moveTo(y+5,h-5),t.lineTo(y-5,h+5),t.stroke()},"rgba(255, 255, 255, 0.7)",1)}}a.hidden=!1}static async load(c){const o=URL.createObjectURL(await w.blob(c));try{const a=new Image;return a.src=o,await a.decode().catch(()=>{throw new W(c,{kind:"notPicture"})}),a}finally{URL.revokeObjectURL(o)}}}export{U as GhostSearchView};
|