diff --git a/dist/BlueMarble.user.js b/dist/BlueMarble.user.js index cfb2b60..877ff96 100644 --- a/dist/BlueMarble.user.js +++ b/dist/BlueMarble.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Blue Marble // @namespace https://github.com/SwingTheVine/ -// @version 0.72.0 +// @version 0.72.4 // @description A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA. // @author SwingTheVine // @license MPL-2.0 @@ -20,4 +20,4 @@ // Wplace --> https://wplace.live // License --> https://www.mozilla.org/en-US/MPL/2.0/ -(()=>{var t,e,n=t=>{throw TypeError(t)},i=(t,e,i)=>e.has(t)?n("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(t):e.set(t,i),s=(t,e,i)=>(((t,e)=>{e.has(t)||n("Cannot access private method")})(t,e),i);t=new WeakSet,e=function(t,e={},n={}){const i=document.createElement(t);this.t?(this.i.appendChild(i),this.o.push(this.i),this.i=i):(this.t=i,this.i=i);for(const[t,n]of Object.entries(e))i[t]=n;for(const[t,e]of Object.entries(n))i[t]=e;return i};var o,a=class{constructor({displayName:t="My template",l:e=0,h:n="",url:i="",file:s=null,coords:o=null,u:a=null,m:r=1e3}={}){this.displayName=t,this.l=e,this.h=n,this.url=i,this.file=s,this.coords=o,this.u=a,this.m=r,this.p=0}async v(){const t=await createImageBitmap(this.file),e=t.width,n=t.height,i=e*n;this.p=i;const s={},o=new OffscreenCanvas(this.m,this.m),a=o.getContext("2d",{M:!0});for(let i=this.coords[3];i0;)n=e[t%i]+n,t=Math.floor(t/i);return n}o=new WeakSet;var c=GM_info.script.name.toString(),l=GM_info.script.version.toString();!function(t){const e=document.createElement("script");e.setAttribute("bm-o",c),e.setAttribute("bm-m","color: cornflowerblue;"),e.textContent=`(${t})();`,document.documentElement.appendChild(e),e.remove()}(()=>{const t=document.currentScript,e=t?.getAttribute("bm-o")||"Blue Marble",n=t?.getAttribute("bm-m")||"",i=new Map;window.addEventListener("message",t=>{const{source:s,endpoint:o,blobID:a,blobData:r,blink:c}=t.data;if(Date.now(),"blue-marble"==s&&a&&r&&!o){const t=i.get(a);"function"==typeof t?t(r):function(...t){(0,console.warn)(...t)}(`%c${e}%c: Attempted to retrieve a blob (%s) from queue, but the blobID was not a function! Skipping...`,n,"",a),i.delete(a)}});const s=window.fetch;window.fetch=async function(...t){const e=await s.apply(this,t),n=e.clone(),o=(t[0]instanceof Request?t[0]?.url:t[0])||"ignore",a=n.headers.get("content-type")||"";if(a.includes("application/json"))n.json().then(t=>{window.postMessage({source:"blue-marble",endpoint:o,jsonData:t},"*")}).catch(t=>{});else if(a.includes("image/")&&!o.includes("openfreemap")){const t=Date.now(),e=await n.blob();return new Promise(s=>{const a=crypto.randomUUID();i.set(a,t=>{s(new Response(t,{headers:n.headers,status:n.status,statusText:n.statusText}))}),window.postMessage({source:"blue-marble",endpoint:o,blobID:a,blobData:e,blink:t})}).catch(t=>{Date.now()})}return e}});var h=GM_getResourceText("CSS-BM-File");GM_addStyle(h);var u=document.createElement("link");u.href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap",u.rel="preload",u.as="style",u.onload=function(){this.onload=null,this.rel="stylesheet"},document.head.appendChild(u),new class{constructor(){this.$=null,this.C=null,this.D="#bm-5"}I(t){return this.C=t,this.$=new MutationObserver(t=>{for(const e of t)for(const t of e.addedNodes)t instanceof HTMLElement&&t.matches?.(this.D)}),this}k(){return this.$}observe(t,e=!1,n=!1){t.observe(this.C,{childList:e,subtree:n})}};var m=new class{constructor(e,n){i(this,t),this.name=e,this.version=n,this.T=null,this.N="bm-a",this.t=null,this.i=null,this.o=[]}S(t){this.T=t}B(){return this.o.length>0&&(this.i=this.o.pop()),this}O(t){t.appendChild(this.t),this.t=null,this.i=null,this.o=[]}L(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"div",{},n)),this}H(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"p",{},n)),this}q(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"small",{},n)),this}j(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"img",{},n)),this}F(n,i={},o=()=>{}){return o(this,s(this,t,e).call(this,"h"+n,{},i)),this}P(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"hr",{},n)),this}R(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"br",{},n)),this}_(n={},i=()=>{}){const o=s(this,t,e).call(this,"label",{textContent:n.textContent??""});delete n.textContent;const a=s(this,t,e).call(this,"input",{type:"checkbox"},n);return o.insertBefore(a,o.firstChild),this.B(),i(this,o,a),this}A(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"button",{},n)),this}V(n={},i=()=>{}){const o=n.title??n.textContent??"Help: No info";delete n.textContent,n.title=`Help: ${o}`;const a={textContent:"?",className:"bm-p",onclick:()=>{this.Y(this.N,o)}};return i(this,s(this,t,e).call(this,"button",a,n)),this}G(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"input",{},n)),this}U(n={},i=()=>{}){const o=n.textContent??"";delete n.textContent;const a=s(this,t,e).call(this,"div"),r=s(this,t,e).call(this,"input",{type:"file",style:"display: none !important; visibility: hidden !important; position: absolute !important; left: -9999px !important; width: 0 !important; height: 0 !important; opacity: 0 !important;"},n);this.B();const c=s(this,t,e).call(this,"button",{textContent:o});return this.B(),this.B(),r.setAttribute("tabindex","-1"),r.setAttribute("aria-hidden","true"),c.addEventListener("click",()=>{r.click()}),r.addEventListener("change",()=>{c.style.maxWidth=`${c.offsetWidth}px`,r.files.length>0?c.textContent=r.files[0].name:c.textContent=o}),i(this,a,r,c),this}W(n={},i=()=>{}){return i(this,s(this,t,e).call(this,"textarea",{},n)),this}Y(t,e,n=!1){const i=document.getElementById(t.replace(/^#/,""));i&&(i instanceof HTMLInputElement?i.value=e:n?i.textContent=e:i.innerHTML=e)}X(t,e){let n,i=!1,s=0,o=null,a=0,r=0,c=0,l=0;if(t=document.querySelector("#"==t?.[0]?t:"#"+t),e=document.querySelector("#"==e?.[0]?e:"#"+e),!t||!e)return void this.J(`Can not drag! ${t?"":"moveMe"} ${t||e?"":"and "}${e?"":"iMoveThings "}was not found!`);const h=()=>{if(i){const e=Math.abs(a-c),n=Math.abs(r-l);(e>.5||n>.5)&&(a=c,r=l,t.style.transform=`translate(${a}px, ${r}px)`,t.style.left="0px",t.style.top="0px",t.style.right=""),o=requestAnimationFrame(h)}};let u=null;const m=(m,d)=>{i=!0,u=t.getBoundingClientRect(),n=m-u.left,s=d-u.top;const p=window.getComputedStyle(t).transform;if(p&&"none"!==p){const t=new DOMMatrix(p);a=t.m41,r=t.m42}else a=u.left,r=u.top;c=a,l=r,document.body.style.userSelect="none",e.classList.add("dragging"),o&&cancelAnimationFrame(o),h()},d=()=>{i=!1,o&&(cancelAnimationFrame(o),o=null),document.body.style.userSelect="",e.classList.remove("dragging")};e.addEventListener("mousedown",function(t){t.preventDefault(),m(t.clientX,t.clientY)}),e.addEventListener("touchstart",function(t){const e=t?.touches?.[0];e&&(m(e.clientX,e.clientY),t.preventDefault())},{passive:!1}),document.addEventListener("mousemove",function(t){i&&u&&(c=t.clientX-n,l=t.clientY-s)},{passive:!0}),document.addEventListener("touchmove",function(t){if(i&&u){const e=t?.touches?.[0];if(!e)return;c=e.clientX-n,l=e.clientY-s,t.preventDefault()}},{passive:!1}),document.addEventListener("mouseup",d),document.addEventListener("touchend",d),document.addEventListener("touchcancel",d)}Z(t){(0,console.info)(`${this.name}: ${t}`),this.Y(this.N,"Status: "+t,!0)}J(t){(0,console.error)(`${this.name}: ${t}`),this.Y(this.N,"Error: "+t,!0)}}(c,l),d=new class{constructor(t,e,n){i(this,o),this.name=t,this.version=e,this.t=n,this.K="1.0.0",this.tt=null,this.et="!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~",this.m=1e3,this.nt=3,this.it=null,this.st=null,this.ot="bm-n",this.rt="div#map canvas.maplibregl-canvas",this.ct=null,this.lt="",this.ht=[],this.ut=null}dt(){if(document.body.contains(this.it))return this.it;document.getElementById(this.ot)?.remove();const t=document.querySelector(this.rt),e=document.createElement("canvas");return e.id=this.ot,e.className="maplibregl-canvas",e.style.position="absolute",e.style.top="0",e.style.left="0",e.style.height=t?.clientHeight*(window.devicePixelRatio||1)+"px",e.style.width=t?.clientWidth*(window.devicePixelRatio||1)+"px",e.height=t?.clientHeight*(window.devicePixelRatio||1),e.width=t?.clientWidth*(window.devicePixelRatio||1),e.style.zIndex="8999",e.style.pointerEvents="none",t?.parentElement?.appendChild(e),this.it=e,window.addEventListener("move",this.bt),window.addEventListener("zoom",this.ft),window.addEventListener("resize",this.wt),this.it}async vt(){return{whoami:this.name.replace(" ",""),scriptVersion:this.version,schemaVersion:this.K,templates:{}}}async xt(t,e,n){this.ut||(this.ut=await this.vt()),this.t.Z(`Creating template at ${n.join(", ")}...`);const i=new a({displayName:e,l:0,h:r(this.tt||0,this.et),file:t,coords:n});i.u=await i.v(this.m),this.ut.templates[`${i.l} ${i.h}`]={name:i.displayName,enabled:!0,tiles:i.u},this.ht=[],this.ht.push(i);const s=(new Intl.NumberFormat).format(i.p);this.t.Z(`Template created at ${n.join(", ")}! Total pixels: ${s}`)}yt(){}async gt(){this.ut||(this.ut=await this.vt())}async Mt(t,e){const n=this.m*this.nt;e=e[0].toString().padStart(4,"0")+","+e[1].toString().padStart(4,"0");const i=this.ht;i.sort((t,e)=>t.l-e.l);const s=i.map(t=>{const n=Object.keys(t.u).filter(t=>t.startsWith(e));if(0===n.length)return null;const i=n.map(e=>t.u[e]);return i?.[0]}).filter(Boolean);if(s.length>0){const t=i.filter(t=>Object.keys(t.u).filter(t=>t.startsWith(e)).length>0).reduce((t,e)=>t+(e.p||0),0),n=(new Intl.NumberFormat).format(t);this.t.Z(`Displaying ${s.length} template${1==s.length?"":"s"}. Total pixels: ${n}`)}const o=await createImageBitmap(t),a=new OffscreenCanvas(n,n),r=a.getContext("2d");r.imageSmoothingEnabled=!1,r.beginPath(),r.rect(0,0,n,n),r.clip(),r.clearRect(0,0,n,n),r.drawImage(o,0,0,n,n);for(const t of s)r.drawImage(t,0,0);return await a.convertToBlob({type:"image/png"})}$t(){}}(c,l,m),p=new class{constructor(t){this.Ct=t,this.Dt=!1,this.It=[],this.kt=[]}Tt(t){window.addEventListener("message",async e=>{const n=e.data,i=n.jsonData;if(!n||"blue-marble"!==n.source)return;if(!n.endpoint)return;const s=n.endpoint?.split("?")[0].split("/").filter(t=>t&&isNaN(Number(t))).filter(t=>t&&!t.includes(".")).pop();switch(s){case"me":if(i.status&&"2"!=i.status?.toString()[0])return void t.J("You are not logged in!\nCould not fetch userdata.");const e=Math.ceil(Math.pow(Math.floor(i.level)*Math.pow(30,.65),1/.65)-i.pixelsPainted);i.id||i.id,this.Ct.tt=i.id,t.Y("bm-f",`Username: ${function(t){const e=document.createElement("div");return e.textContent=t,e.innerHTML}(i.name)}`),t.Y("bm-b",`Droplets: ${(new Intl.NumberFormat).format(i.droplets)}`),t.Y("bm-6",`Next level in ${(new Intl.NumberFormat).format(e)} pixel${1==e?"":"s"}`);break;case"pixel":const s=n.endpoint.split("?")[0].split("/").filter(t=>t&&!isNaN(Number(t))),r=new URLSearchParams(n.endpoint.split("?")[1]),c=[r.get("x"),r.get("y")];if(this.It.length&&(!s.length||!c.length))return void t.J("Coordinates are malformed!\nDid you try clicking the canvas first?");this.It=[...s,...c];const l=(o=s,a=c,[parseInt(o[0])%4*1e3+parseInt(a[0]),parseInt(o[1])%4*1e3+parseInt(a[1])]),h=document.querySelectorAll("span");for(const t of h)if(t.textContent.trim().includes(`${l[0]}, ${l[1]}`)){let e=document.querySelector("#bm-5");const n=`(Tl X: ${s[0]}, Tl Y: ${s[1]}, Px X: ${c[0]}, Px Y: ${c[1]})`;e?e.textContent=n:(e=document.createElement("span"),e.id="bm-5",e.textContent=n,e.style="margin-left: calc(var(--spacing)*3); font-size: small;",t.parentNode.parentNode.parentNode.insertAdjacentElement("afterend",e))}break;case"tiles":let u=n.endpoint.split("/");u=[parseInt(u[u.length-2]),parseInt(u[u.length-1].replace(".png",""))];const m=n.blobID,d=n.blobData,p=await this.Ct.Mt(d,u);window.postMessage({source:"blue-marble",blobID:m,blobData:p,blink:n.blink});break;case"robots":this.Dt="false"==i.userscript?.toString().toLowerCase()}var o,a})}}(d);m.S(p),function(){let t=!1;m.L({id:"bm-l",style:"top: 10px; right: 75px;"}).L({id:"bm-7"}).L({id:"bm-g"}).B().j({alt:"Blue Marble Icon - Click to minimize/maximize",src:"https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/assets/Favicon.png",style:"cursor: pointer;"},(e,n)=>{n.addEventListener("click",()=>{t=!t;const i=document.querySelector("#bm-l"),s=document.querySelector("#bm-7"),o=document.querySelector("#bm-g"),a=document.querySelector("#bm-8"),r=document.querySelector("#bm-c"),c=document.querySelector("#bm-d"),l=document.querySelectorAll("#bm-8 input");t||(i.style.width="auto",i.style.maxWidth="300px",i.style.minWidth="200px",i.style.padding="10px"),["#bm-l h1","#bm-4","#bm-l hr","#bm-3 > *:not(#bm-8)","#bm-2","#bm-1",`#${e.N}`].forEach(e=>{document.querySelectorAll(e).forEach(e=>{e.style.display=t?"none":""})}),t?(a&&(a.style.display="none"),r&&(r.style.display="none"),c&&(c.style.display="none"),l.forEach(t=>{t.style.display="none"}),i.style.width="60px",i.style.height="76px",i.style.maxWidth="60px",i.style.minWidth="60px",i.style.padding="8px",n.style.marginLeft="3px",s.style.textAlign="center",s.style.margin="0",s.style.marginBottom="0",o&&(o.style.display="",o.style.marginBottom="0.25em")):(a&&(a.style.display="",a.style.flexDirection="",a.style.justifyContent="",a.style.alignItems="",a.style.gap="",a.style.textAlign="",a.style.margin=""),r&&(r.style.display=""),c&&(c.style.display="",c.style.marginTop=""),l.forEach(t=>{t.style.display=""}),n.style.marginLeft="",i.style.padding="10px",s.style.textAlign="",s.style.margin="",s.style.marginBottom="",o&&(o.style.marginBottom="0.5em"),i.style.width="",i.style.height=""),n.alt=t?"Blue Marble Icon - Minimized (Click to maximize)":"Blue Marble Icon - Maximized (Click to minimize)"})}).B().F(1,{textContent:c}).B().B().P().B().L({id:"bm-4"}).H({id:"bm-f",textContent:"Username:"}).B().H({id:"bm-b",textContent:"Droplets:"}).B().H({id:"bm-6",textContent:"Next level in..."}).B().B().P().B().L({id:"bm-3"}).L({id:"bm-8"}).A({id:"bm-c",className:"bm-p",style:"margin-top: 0;",innerHTML:''},(t,e)=>{e.onclick=()=>{const e=t.T?.It;e?.[0]?(t.Y("bm-h",e?.[0]||""),t.Y("bm-i",e?.[1]||""),t.Y("bm-j",e?.[2]||""),t.Y("bm-k",e?.[3]||"")):t.J("Coordinates are malformed! Did you try clicking on the canvas first?")}}).B().G({type:"number",id:"bm-h",placeholder:"Tl X",min:0,max:2047,step:1,required:!0}).B().G({type:"number",id:"bm-i",placeholder:"Tl Y",min:0,max:2047,step:1,required:!0}).B().G({type:"number",id:"bm-j",placeholder:"Px X",min:0,max:2047,step:1,required:!0}).B().G({type:"number",id:"bm-k",placeholder:"Px Y",min:0,max:2047,step:1,required:!0}).B().B().U({id:"bm-2",textContent:"Upload Template",accept:"image/png, image/jpeg, image/webp, image/bmp, image/gif"}).B().L({id:"bm-0"}).A({id:"bm-d",textContent:"Enable"},(t,e)=>{e.onclick=()=>{const e=document.querySelector("#bm-2"),n=document.querySelector("#bm-h");if(!n.checkValidity())return n.reportValidity(),void t.J("Coordinates are malformed! Did you try clicking on the canvas first?");const i=document.querySelector("#bm-i");if(!i.checkValidity())return i.reportValidity(),void t.J("Coordinates are malformed! Did you try clicking on the canvas first?");const s=document.querySelector("#bm-j");if(!s.checkValidity())return s.reportValidity(),void t.J("Coordinates are malformed! Did you try clicking on the canvas first?");const o=document.querySelector("#bm-k");if(!o.checkValidity())return o.reportValidity(),void t.J("Coordinates are malformed! Did you try clicking on the canvas first?");e?.files[0]?(d.xt(e.files[0],e.files[0]?.name.replace(/\.[^/.]+$/,""),[Number(n.value),Number(i.value),Number(s.value),Number(o.value)]),t.Z("Drew to canvas!")):t.J("No file selected!")}}).B().B().W({id:m.N,placeholder:`Status: Sleeping...\nVersion: ${l}`,readOnly:!0}).B().L({id:"bm-1"}).L().A({id:"bm-9",className:"bm-p",innerHTML:"🎨",title:"Template Color Converter"},(t,e)=>{e.addEventListener("click",()=>{window.open("https://pepoafonso.github.io/color_converter_wplace/","_blank","noopener noreferrer")})}).B().B().q({textContent:"Made by SwingTheVine",style:"margin-top: auto;"}).B().B().B().O(document.body)}(),m.X("#bm-l","#bm-g"),p.Tt(m),new MutationObserver((t,e)=>{const n=document.querySelector("#color-1");if(!n)return;let i=document.querySelector("#bm-e");i||(i=document.createElement("button"),i.id="bm-e",i.textContent="Move ↑",i.className="btn btn-soft",i.onclick=function(){const t=this.parentNode.parentNode.parentNode.parentNode,e="Move ↑"==this.textContent;t.parentNode.className=t.parentNode.className.replace(e?"bottom":"top",e?"top":"bottom"),t.style.borderTopLeftRadius=e?"0px":"var(--radius-box)",t.style.borderTopRightRadius=e?"0px":"var(--radius-box)",t.style.borderBottomLeftRadius=e?"var(--radius-box)":"0px",t.style.borderBottomRightRadius=e?"var(--radius-box)":"0px",this.textContent=e?"Move ↓":"Move ↑"},n.parentNode.parentNode.parentNode.parentNode.querySelector("h2").parentNode.appendChild(i))}).observe(document.body,{childList:!0,subtree:!0}),function(...t){(0,console.log)(...t)}(`%c${c}%c (${l}) userscript has loaded!`,"color: cornflowerblue;","")})(); +(()=>{var t,e,n=t=>{throw TypeError(t)},i=(t,e,i)=>e.has(t)?n("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(t):e.set(t,i),o=(t,e,i)=>(((t,e)=>{e.has(t)||n("Cannot access private method")})(t,e),i);t=new WeakSet,e=function(t,e={},n={}){const i=document.createElement(t);this.t?(this.i.appendChild(i),this.o.push(this.i),this.i=i):(this.t=i,this.i=i);for(const[t,n]of Object.entries(e))i[t]=n;for(const[t,e]of Object.entries(n))i[t]=e;return i};var s,a=class{constructor({displayName:t="My template",l:e=0,h:n="",url:i="",file:o=null,coords:s=null,m:a=null,u:r=1e3}={}){this.displayName=t,this.l=e,this.h=n,this.url=i,this.file=o,this.coords=s,this.m=a,this.u=r,this.p=0}async $(){console.log("Template coordinates:",this.coords);const t=await createImageBitmap(this.file),e=t.width,n=t.height,i=e*n;console.log(`Template pixel analysis - Dimensions: ${e}×${n} = ${i.toLocaleString()} pixels`),this.p=i;const o={},s=new OffscreenCanvas(this.u,this.u),a=s.getContext("2d",{v:!0});for(let i=this.coords[3];iURL.revokeObjectURL(u),6e4);const d=a.getImageData(0,0,l,h);for(let t=0;t0;)n=e[t%i]+n,t=Math.floor(t/i);return n}s=new WeakSet;var c=GM_info.script.name.toString(),l=GM_info.script.version.toString();!function(t){const e=document.createElement("script");e.setAttribute("bm-o",c),e.setAttribute("bm-m","color: cornflowerblue;"),e.textContent=`(${t})();`,document.documentElement.appendChild(e),e.remove()}(()=>{const t=document.currentScript,e=t?.getAttribute("bm-o")||"Blue Marble",n=t?.getAttribute("bm-m")||"",i=new Map;window.addEventListener("message",t=>{const{source:o,endpoint:s,blobID:a,blobData:r,blink:c}=t.data,l=Date.now()-c;if(console.groupCollapsed(`%c${e}%c: ${i.size} Recieved IMAGE message about blob "${a}"`,n,""),console.log(`Blob fetch took %c${String(Math.floor(l/6e4)).padStart(2,"0")}:${String(Math.floor(l/1e3)%60).padStart(2,"0")}.${String(l%1e3).padStart(3,"0")}%c MM:SS.mmm`,n,""),console.log(i),console.groupEnd(),"blue-marble"==o&&a&&r&&!s){const t=i.get(a);"function"==typeof t?t(r):function(...t){(0,console.warn)(...t)}(`%c${e}%c: Attempted to retrieve a blob (%s) from queue, but the blobID was not a function! Skipping...`,n,"",a),i.delete(a)}});const o=window.fetch;window.fetch=async function(...t){const s=await o.apply(this,t),a=s.clone(),r=(t[0]instanceof Request?t[0]?.url:t[0])||"ignore",c=a.headers.get("content-type")||"";if(c.includes("application/json"))console.log(`%c${e}%c: Sending JSON message about endpoint "${r}"`,n,""),a.json().then(t=>{window.postMessage({source:"blue-marble",endpoint:r,jsonData:t},"*")}).catch(t=>{console.error(`%c${e}%c: Failed to parse JSON: `,n,"",t)});else if(c.includes("image/")&&!r.includes("openfreemap")){const t=Date.now(),o=await a.blob();return console.log(`%c${e}%c: ${i.size} Sending IMAGE message about endpoint "${r}"`,n,""),new Promise(s=>{const c=crypto.randomUUID();i.set(c,t=>{s(new Response(t,{headers:a.headers,status:a.status,statusText:a.statusText})),console.log(`%c${e}%c: ${i.size} Processed blob "${c}"`,n,"")}),window.postMessage({source:"blue-marble",endpoint:r,blobID:c,blobData:o,blink:t})}).catch(o=>{const s=Date.now();console.error(`%c${e}%c: Failed to Promise blob!`,n,""),console.groupCollapsed(`%c${e}%c: Details of failed blob Promise:`,n,""),console.log(`Endpoint: ${r}\nThere are ${i.size} blobs processing...\nBlink: ${t.toLocaleString()}\nTime Since Blink: ${String(Math.floor(s/6e4)).padStart(2,"0")}:${String(Math.floor(s/1e3)%60).padStart(2,"0")}.${String(s%1e3).padStart(3,"0")} MM:SS.mmm`),console.error("Exception stack:",o),console.groupEnd()})}return s}});var h=GM_getResourceText("CSS-BM-File");GM_addStyle(h);var m=document.createElement("link");m.href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap",m.rel="preload",m.as="style",m.onload=function(){this.onload=null,this.rel="stylesheet"},document.head.appendChild(m),new class{constructor(){this.M=null,this.S=null,this.C="#bm-5"}D(t){return this.S=t,this.M=new MutationObserver(t=>{for(const e of t)for(const t of e.addedNodes)t instanceof HTMLElement&&t.matches?.(this.C)}),this}T(){return this.M}observe(t,e=!1,n=!1){t.observe(this.S,{childList:e,subtree:n})}};var u=new class{constructor(e,n){i(this,t),this.name=e,this.version=n,this.k=null,this.I="bm-a",this.t=null,this.i=null,this.o=[]}N(t){this.k=t}O(){return this.o.length>0&&(this.i=this.o.pop()),this}B(t){t.appendChild(this.t),this.t=null,this.i=null,this.o=[]}L(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"div",{},n)),this}P(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"p",{},n)),this}H(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"small",{},n)),this}R(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"img",{},n)),this}F(n,i={},s=()=>{}){return s(this,o(this,t,e).call(this,"h"+n,{},i)),this}Y(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"hr",{},n)),this}j(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"br",{},n)),this}q(n={},i=()=>{}){const s=o(this,t,e).call(this,"label",{textContent:n.textContent??""});delete n.textContent;const a=o(this,t,e).call(this,"input",{type:"checkbox"},n);return s.insertBefore(a,s.firstChild),this.O(),i(this,s,a),this}G(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"button",{},n)),this}X(n={},i=()=>{}){const s=n.title??n.textContent??"Help: No info";delete n.textContent,n.title=`Help: ${s}`;const a={textContent:"?",className:"bm-p",onclick:()=>{this._(this.I,s)}};return i(this,o(this,t,e).call(this,"button",a,n)),this}A(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"input",{},n)),this}J(n={},i=()=>{}){const s=n.textContent??"";delete n.textContent;const a=o(this,t,e).call(this,"div"),r=o(this,t,e).call(this,"input",{type:"file",style:"display: none !important; visibility: hidden !important; position: absolute !important; left: -9999px !important; width: 0 !important; height: 0 !important; opacity: 0 !important;"},n);this.O();const c=o(this,t,e).call(this,"button",{textContent:s});return this.O(),this.O(),r.setAttribute("tabindex","-1"),r.setAttribute("aria-hidden","true"),c.addEventListener("click",()=>{r.click()}),r.addEventListener("change",()=>{c.style.maxWidth=`${c.offsetWidth}px`,r.files.length>0?c.textContent=r.files[0].name:c.textContent=s}),i(this,a,r,c),this}U(n={},i=()=>{}){return i(this,o(this,t,e).call(this,"textarea",{},n)),this}_(t,e,n=!1){const i=document.getElementById(t.replace(/^#/,""));i&&(i instanceof HTMLInputElement?i.value=e:n?i.textContent=e:i.innerHTML=e)}V(t,e){let n,i=!1,o=0,s=null,a=0,r=0,c=0,l=0;if(t=document.querySelector("#"==t?.[0]?t:"#"+t),e=document.querySelector("#"==e?.[0]?e:"#"+e),!t||!e)return void this.W(`Can not drag! ${t?"":"moveMe"} ${t||e?"":"and "}${e?"":"iMoveThings "}was not found!`);const h=()=>{if(i){const e=Math.abs(a-c),n=Math.abs(r-l);(e>.5||n>.5)&&(a=c,r=l,t.style.transform=`translate(${a}px, ${r}px)`,t.style.left="0px",t.style.top="0px",t.style.right=""),s=requestAnimationFrame(h)}};let m=null;const u=(u,d)=>{i=!0,m=t.getBoundingClientRect(),n=u-m.left,o=d-m.top;const p=window.getComputedStyle(t).transform;if(p&&"none"!==p){const t=new DOMMatrix(p);a=t.m41,r=t.m42}else a=m.left,r=m.top;c=a,l=r,document.body.style.userSelect="none",e.classList.add("dragging"),s&&cancelAnimationFrame(s),h()},d=()=>{i=!1,s&&(cancelAnimationFrame(s),s=null),document.body.style.userSelect="",e.classList.remove("dragging")};e.addEventListener("mousedown",function(t){t.preventDefault(),u(t.clientX,t.clientY)}),e.addEventListener("touchstart",function(t){const e=t?.touches?.[0];e&&(u(e.clientX,e.clientY),t.preventDefault())},{passive:!1}),document.addEventListener("mousemove",function(t){i&&m&&(c=t.clientX-n,l=t.clientY-o)},{passive:!0}),document.addEventListener("touchmove",function(t){if(i&&m){const e=t?.touches?.[0];if(!e)return;c=e.clientX-n,l=e.clientY-o,t.preventDefault()}},{passive:!1}),document.addEventListener("mouseup",d),document.addEventListener("touchend",d),document.addEventListener("touchcancel",d)}Z(t){(0,console.info)(`${this.name}: ${t}`),this._(this.I,"Status: "+t,!0)}W(t){(0,console.error)(`${this.name}: ${t}`),this._(this.I,"Error: "+t,!0)}}(c,l),d=new class{constructor(t,e,n){i(this,s),this.name=t,this.version=e,this.t=n,this.K="1.0.0",this.tt=null,this.et="!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~",this.u=1e3,this.nt=3,this.it=null,this.ot=null,this.st="bm-n",this.rt="div#map canvas.maplibregl-canvas",this.ct=null,this.lt="",this.ht=[],this.ut=null}dt(){if(document.body.contains(this.it))return this.it;document.getElementById(this.st)?.remove();const t=document.querySelector(this.rt),e=document.createElement("canvas");return e.id=this.st,e.className="maplibregl-canvas",e.style.position="absolute",e.style.top="0",e.style.left="0",e.style.height=t?.clientHeight*(window.devicePixelRatio||1)+"px",e.style.width=t?.clientWidth*(window.devicePixelRatio||1)+"px",e.height=t?.clientHeight*(window.devicePixelRatio||1),e.width=t?.clientWidth*(window.devicePixelRatio||1),e.style.zIndex="8999",e.style.pointerEvents="none",t?.parentElement?.appendChild(e),this.it=e,window.addEventListener("move",this.bt),window.addEventListener("zoom",this.ft),window.addEventListener("resize",this.wt),this.it}async $t(){return{whoami:this.name.replace(" ",""),scriptVersion:this.version,schemaVersion:this.K,templates:{}}}async gt(t,e,n){this.ut||(this.ut=await this.$t(),console.log("Creating JSON...")),this.t.Z(`Creating template at ${n.join(", ")}...`);const i=new a({displayName:e,l:0,h:r(this.tt||0,this.et),file:t,coords:n});i.m=await i.$(this.u),this.ut.templates[`${i.l} ${i.h}`]={name:i.displayName,enabled:!0,tiles:i.m},this.ht=[],this.ht.push(i);const o=(new Intl.NumberFormat).format(i.p);this.t.Z(`Template created at ${n.join(", ")}! Total pixels: ${o}`),console.log(Object.keys(this.ut.templates).length),console.log(this.ut),console.log(this.ht)}vt(){}async xt(){this.ut||(this.ut=await this.$t(),console.log("Creating JSON..."))}async yt(t,e){const n=this.u*this.nt;e=e[0].toString().padStart(4,"0")+","+e[1].toString().padStart(4,"0"),console.log(`Searching for templates in tile: "${e}"`);const i=this.ht;i.sort((t,e)=>t.l-e.l),console.log(i);const o=i.map(t=>{const n=Object.keys(t.m).filter(t=>t.startsWith(e));if(0===n.length)return null;const i=n.map(e=>{const n=e.split(",");return{Mt:t.m[e],St:[n[0],n[1]],Ct:[n[2],n[3]]}});return i?.[0]}).filter(Boolean);if(console.log(o),o.length>0){const t=i.filter(t=>Object.keys(t.m).filter(t=>t.startsWith(e)).length>0).reduce((t,e)=>t+(e.p||0),0),n=(new Intl.NumberFormat).format(t);this.t.Z(`Displaying ${o.length} template${1==o.length?"":"s"}. Total pixels: ${n}`)}const s=await createImageBitmap(t),a=new OffscreenCanvas(n,n),r=a.getContext("2d");r.imageSmoothingEnabled=!1,r.beginPath(),r.rect(0,0,n,n),r.clip(),r.clearRect(0,0,n,n),r.drawImage(s,0,0,n,n);for(const t of o)console.log("Template:"),console.log(t),console.log(`${Number(t.Ct[0])}, ${Number(t.Ct[1])}`),r.drawImage(t.Mt,Number(t.Ct[0])*this.nt,Number(t.Ct[1])*this.nt);return await a.convertToBlob({type:"image/png"})}Dt(){}}(c,l,u),p=new class{constructor(t){this.Tt=t,this.kt=!1,this.It=[],this.Nt=[]}Ot(t){window.addEventListener("message",async e=>{const n=e.data,i=n.jsonData;if(!n||"blue-marble"!==n.source)return;if(!n.endpoint)return;const o=n.endpoint?.split("?")[0].split("/").filter(t=>t&&isNaN(Number(t))).filter(t=>t&&!t.includes(".")).pop();switch(console.log('%cBlue Marble%c: Recieved message about "%s"',"color: cornflowerblue;","",o),o){case"me":if(i.status&&"2"!=i.status?.toString()[0])return void t.W("You are not logged in!\nCould not fetch userdata.");const e=Math.ceil(Math.pow(Math.floor(i.level)*Math.pow(30,.65),1/.65)-i.pixelsPainted);console.log(i.id),(i.id||0===i.id)&&console.log(r(i.id,"!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~")),this.Tt.tt=i.id,t._("bm-f",`Username: ${function(t){const e=document.createElement("div");return e.textContent=t,e.innerHTML}(i.name)}`),t._("bm-b",`Droplets: ${(new Intl.NumberFormat).format(i.droplets)}`),t._("bm-6",`Next level in ${(new Intl.NumberFormat).format(e)} pixel${1==e?"":"s"}`);break;case"pixel":const o=n.endpoint.split("?")[0].split("/").filter(t=>t&&!isNaN(Number(t))),c=new URLSearchParams(n.endpoint.split("?")[1]),l=[c.get("x"),c.get("y")];if(this.It.length&&(!o.length||!l.length))return void t.W("Coordinates are malformed!\nDid you try clicking the canvas first?");this.It=[...o,...l];const h=(s=o,a=l,[parseInt(s[0])%4*1e3+parseInt(a[0]),parseInt(s[1])%4*1e3+parseInt(a[1])]),m=document.querySelectorAll("span");for(const t of m)if(t.textContent.trim().includes(`${h[0]}, ${h[1]}`)){let e=document.querySelector("#bm-5");const n=`(Tl X: ${o[0]}, Tl Y: ${o[1]}, Px X: ${l[0]}, Px Y: ${l[1]})`;e?e.textContent=n:(e=document.createElement("span"),e.id="bm-5",e.textContent=n,e.style="margin-left: calc(var(--spacing)*3); font-size: small;",t.parentNode.parentNode.parentNode.insertAdjacentElement("afterend",e))}break;case"tiles":let u=n.endpoint.split("/");u=[parseInt(u[u.length-2]),parseInt(u[u.length-1].replace(".png",""))];const d=n.blobID,p=n.blobData,b=await this.Tt.yt(p,u);window.postMessage({source:"blue-marble",blobID:d,blobData:b,blink:n.blink});break;case"robots":this.kt="false"==i.userscript?.toString().toLowerCase();break}var s,a})}}(d);u.N(p),function(){let t=!1;u.L({id:"bm-l",style:"top: 10px; right: 75px;"}).L({id:"bm-7"}).L({id:"bm-g"}).O().R({alt:"Blue Marble Icon - Click to minimize/maximize",src:"https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/assets/Favicon.png",style:"cursor: pointer;"},(e,n)=>{n.addEventListener("click",()=>{t=!t;const i=document.querySelector("#bm-l"),o=document.querySelector("#bm-7"),s=document.querySelector("#bm-g"),a=document.querySelector("#bm-8"),r=document.querySelector("#bm-c"),c=document.querySelector("#bm-d"),l=document.querySelectorAll("#bm-8 input");t||(i.style.width="auto",i.style.maxWidth="300px",i.style.minWidth="200px",i.style.padding="10px"),["#bm-l h1","#bm-4","#bm-l hr","#bm-3 > *:not(#bm-8)","#bm-2","#bm-1",`#${e.I}`].forEach(e=>{document.querySelectorAll(e).forEach(e=>{e.style.display=t?"none":""})}),t?(a&&(a.style.display="none"),r&&(r.style.display="none"),c&&(c.style.display="none"),l.forEach(t=>{t.style.display="none"}),i.style.width="60px",i.style.height="76px",i.style.maxWidth="60px",i.style.minWidth="60px",i.style.padding="8px",n.style.marginLeft="3px",o.style.textAlign="center",o.style.margin="0",o.style.marginBottom="0",s&&(s.style.display="",s.style.marginBottom="0.25em")):(a&&(a.style.display="",a.style.flexDirection="",a.style.justifyContent="",a.style.alignItems="",a.style.gap="",a.style.textAlign="",a.style.margin=""),r&&(r.style.display=""),c&&(c.style.display="",c.style.marginTop=""),l.forEach(t=>{t.style.display=""}),n.style.marginLeft="",i.style.padding="10px",o.style.textAlign="",o.style.margin="",o.style.marginBottom="",s&&(s.style.marginBottom="0.5em"),i.style.width="",i.style.height=""),n.alt=t?"Blue Marble Icon - Minimized (Click to maximize)":"Blue Marble Icon - Maximized (Click to minimize)"})}).O().F(1,{textContent:c}).O().O().Y().O().L({id:"bm-4"}).P({id:"bm-f",textContent:"Username:"}).O().P({id:"bm-b",textContent:"Droplets:"}).O().P({id:"bm-6",textContent:"Next level in..."}).O().O().Y().O().L({id:"bm-3"}).L({id:"bm-8"}).G({id:"bm-c",className:"bm-p",style:"margin-top: 0;",innerHTML:''},(t,e)=>{e.onclick=()=>{const e=t.k?.It;e?.[0]?(t._("bm-h",e?.[0]||""),t._("bm-i",e?.[1]||""),t._("bm-j",e?.[2]||""),t._("bm-k",e?.[3]||"")):t.W("Coordinates are malformed! Did you try clicking on the canvas first?")}}).O().A({type:"number",id:"bm-h",placeholder:"Tl X",min:0,max:2047,step:1,required:!0}).O().A({type:"number",id:"bm-i",placeholder:"Tl Y",min:0,max:2047,step:1,required:!0}).O().A({type:"number",id:"bm-j",placeholder:"Px X",min:0,max:2047,step:1,required:!0}).O().A({type:"number",id:"bm-k",placeholder:"Px Y",min:0,max:2047,step:1,required:!0}).O().O().J({id:"bm-2",textContent:"Upload Template",accept:"image/png, image/jpeg, image/webp, image/bmp, image/gif"}).O().L({id:"bm-0"}).G({id:"bm-d",textContent:"Enable"},(t,e)=>{e.onclick=()=>{const e=document.querySelector("#bm-2"),n=document.querySelector("#bm-h");if(!n.checkValidity())return n.reportValidity(),void t.W("Coordinates are malformed! Did you try clicking on the canvas first?");const i=document.querySelector("#bm-i");if(!i.checkValidity())return i.reportValidity(),void t.W("Coordinates are malformed! Did you try clicking on the canvas first?");const o=document.querySelector("#bm-j");if(!o.checkValidity())return o.reportValidity(),void t.W("Coordinates are malformed! Did you try clicking on the canvas first?");const s=document.querySelector("#bm-k");if(!s.checkValidity())return s.reportValidity(),void t.W("Coordinates are malformed! Did you try clicking on the canvas first?");e?.files[0]?(d.gt(e.files[0],e.files[0]?.name.replace(/\.[^/.]+$/,""),[Number(n.value),Number(i.value),Number(o.value),Number(s.value)]),t.Z("Drew to canvas!")):t.W("No file selected!")}}).O().O().U({id:u.I,placeholder:`Status: Sleeping...\nVersion: ${l}`,readOnly:!0}).O().L({id:"bm-1"}).L().G({id:"bm-9",className:"bm-p",innerHTML:"🎨",title:"Template Color Converter"},(t,e)=>{e.addEventListener("click",()=>{window.open("https://pepoafonso.github.io/color_converter_wplace/","_blank","noopener noreferrer")})}).O().O().H({textContent:"Made by SwingTheVine",style:"margin-top: auto;"}).O().O().O().B(document.body)}(),u.V("#bm-l","#bm-g"),p.Ot(u),new MutationObserver((t,e)=>{const n=document.querySelector("#color-1");if(!n)return;let i=document.querySelector("#bm-e");i||(i=document.createElement("button"),i.id="bm-e",i.textContent="Move ↑",i.className="btn btn-soft",i.onclick=function(){const t=this.parentNode.parentNode.parentNode.parentNode,e="Move ↑"==this.textContent;t.parentNode.className=t.parentNode.className.replace(e?"bottom":"top",e?"top":"bottom"),t.style.borderTopLeftRadius=e?"0px":"var(--radius-box)",t.style.borderTopRightRadius=e?"0px":"var(--radius-box)",t.style.borderBottomLeftRadius=e?"var(--radius-box)":"0px",t.style.borderBottomRightRadius=e?"var(--radius-box)":"0px",this.textContent=e?"Move ↓":"Move ↑"},n.parentNode.parentNode.parentNode.parentNode.querySelector("h2").parentNode.appendChild(i))}).observe(document.body,{childList:!0,subtree:!0}),function(...t){(0,console.log)(...t)}(`%c${c}%c (${l}) userscript has loaded!`,"color: cornflowerblue;","")})(); \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 5d7d1dd..101dde0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -44,7 +44,7 @@ Software License: MPL-2.0 Contact Me WakaTime -Total Patches +Total Patches Total Lines of Code Total Comments Compression diff --git a/package-lock.json b/package-lock.json index e8c0061..417762c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wplace-bluemarble", - "version": "0.71.0", + "version": "0.72.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wplace-bluemarble", - "version": "0.71.0", + "version": "0.72.4", "devDependencies": { "esbuild": "^0.25.0", "terser": "^5.43.1" diff --git a/package.json b/package.json index 2c05718..0a14808 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wplace-bluemarble", - "version": "0.72.0", + "version": "0.72.4", "type": "module", "scripts": { "build": "node build/build.js", diff --git a/src/BlueMarble.meta.js b/src/BlueMarble.meta.js index d91591a..fa8dafb 100644 --- a/src/BlueMarble.meta.js +++ b/src/BlueMarble.meta.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Blue Marble // @namespace https://github.com/SwingTheVine/ -// @version 0.72.0 +// @version 0.72.4 // @description A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA. // @author SwingTheVine // @license MPL-2.0 diff --git a/src/Template.js b/src/Template.js index c6124c2..a4915a6 100644 --- a/src/Template.js +++ b/src/Template.js @@ -1,37 +1,10 @@ -/** An instance of a template with comprehensive pixel counting and statistics. - * Handles all mathematics, manipulation, and statistical analysis regarding a single template. - * - * TEMPLATE FEATURES: - * - Automatic pixel counting and dimension analysis - * - Tile-based template chunking for efficient rendering - * - Statistical information storage and retrieval - * - Bitmap processing with configurable scaling - * - Memory-efficient template tile generation - * - * PIXEL COUNTING SYSTEM: - * - Calculates total pixel count (width × height) during template creation - * - Stores pixel count for statistical display and analysis - * - Integrates with template manager for aggregate statistics - * - Provides formatted pixel count information to user interface - * +/** An instance of a template. + * Handles all mathematics, manipulation, and analysis regarding a single template. * @since 0.65.2 */ export default class Template { + /** The constructor for the {@link Template} class with enhanced pixel tracking. - * - * Initializes a new template instance with all necessary properties for rendering - * and statistical analysis. The pixel counting system is initialized here and - * populated during the template creation process. - * - * PIXEL COUNTING INTEGRATION: - * The pixelCount property is automatically calculated during createTemplateTiles() - * and represents the total number of pixels in the source image (width × height). - * This information is used for: - * - User interface statistics display - * - Template comparison and analysis - * - Performance optimization decisions - * - Memory usage estimation - * * @param {Object} [params={}] - Object containing all optional parameters * @param {string} [params.displayName='My template'] - The display name of the template * @param {number} [params.sortID=0] - The sort number of the template for rendering priority @@ -62,38 +35,10 @@ export default class Template { this.coords = coords; this.chunked = chunked; this.tileSize = tileSize; - this.pixelCount = 0; // Total pixel count in template (automatically calculated during createTemplateTiles) + this.pixelCount = 0; // Total pixel count in template } - /** Creates chunks of the template for each tile with integrated pixel counting system. - * - * This method processes the template image and performs several critical operations: - * 1. PIXEL ANALYSIS: Calculates total pixel count (width × height) for statistical purposes - * 2. TILE CHUNKING: Divides the template into tile-sized chunks for efficient rendering - * 3. BITMAP PROCESSING: Applies scaling and filtering for optimal display quality - * 4. MEMORY OPTIMIZATION: Creates efficient ImageBitmap objects for each tile segment - * - * PIXEL COUNTING IMPLEMENTATION: - * The pixel counting system calculates the total number of pixels in the source image - * by multiplying the bitmap width by height. This information is stored in the - * pixelCount property and used throughout the application for: - * - User interface statistics display - * - Template comparison and analysis - * - Performance monitoring and optimization - * - Memory usage estimation and management - * - * TECHNICAL DETAILS: - * - Uses createImageBitmap() for efficient image processing - * - Applies 3x scaling factor (shreadSize) for pixel art enhancement - * - Processes images in tile-sized chunks for memory efficiency - * - Maintains pixel-perfect rendering with nearest-neighbor sampling - * - Handles coordinate transformation between template and tile coordinate systems - * - * PERFORMANCE CONSIDERATIONS: - * - Large templates are processed incrementally to avoid memory issues - * - Bitmap creation uses OffscreenCanvas for optimal performance - * - Pixel counting is performed once during initial processing - * - Results are cached in the pixelCount property for repeated access + /** Creates chunks of the template for each tile. * * @returns {Object} Collection of template bitmaps organized by tile coordinates * @since 0.65.4 @@ -106,7 +51,6 @@ export default class Template { const imageWidth = bitmap.width; const imageHeight = bitmap.height; - // ==================== PIXEL COUNTING SYSTEM ==================== // Calculate total pixel count using standard width × height formula // This provides essential statistical information for the user interface const totalPixels = imageWidth * imageHeight; @@ -123,75 +67,53 @@ export default class Template { // For every tile... for (let pixelY = this.coords[3]; pixelY < imageHeight + this.coords[3]; ) { + // Draws the partial tile first, if any // This calculates the size based on which is smaller: // A. The top left corner of the current tile to the bottom right corner of the current tile // B. The top left corner of the current tile to the bottom right corner of the image - const drawSizeY = Math.min( - this.tileSize - (pixelY % this.tileSize), - imageHeight - (pixelY - this.coords[3]) - ); - console.log( - `Math.min(${this.tileSize} - (${pixelY} % ${ - this.tileSize - }), ${imageHeight} - (${pixelY - this.coords[3]}))` - ); + const drawSizeY = Math.min(this.tileSize - (pixelY % this.tileSize), imageHeight - (pixelY - this.coords[3])); - for ( - let pixelX = this.coords[2]; - pixelX < imageWidth + this.coords[2]; + console.log(`Math.min(${this.tileSize} - (${pixelY} % ${this.tileSize}), ${imageHeight} - (${pixelY - this.coords[3]}))`); + + for (let pixelX = this.coords[2]; pixelX < imageWidth + this.coords[2];) { - ) { console.log(`Pixel X: ${pixelX}\nPixel Y: ${pixelY}`); // Draws the partial tile first, if any // This calculates the size based on which is smaller: // A. The top left corner of the current tile to the bottom right corner of the current tile // B. The top left corner of the current tile to the bottom right corner of the image - const drawSizeX = Math.min( - this.tileSize - (pixelX % this.tileSize), - imageWidth - (pixelX - this.coords[2]) - ); - console.log( - `Math.min(${this.tileSize} - (${pixelX} % ${ - this.tileSize - }), ${imageWidth} - (${pixelX - this.coords[2]}))` - ); + const drawSizeX = Math.min(this.tileSize - (pixelX % this.tileSize), imageWidth - (pixelX - this.coords[2])); + + console.log(`Math.min(${this.tileSize} - (${pixelX} % ${this.tileSize}), ${imageWidth} - (${pixelX - this.coords[2]}))`); console.log(`Draw Size X: ${drawSizeX}\nDraw Size Y: ${drawSizeY}`); // Change the canvas size and wipe the canvas - const canvasWidth = - drawSizeX * shreadSize + (pixelX % this.tileSize) * shreadSize; - const canvasHeight = - drawSizeY * shreadSize + (pixelY % this.tileSize) * shreadSize; + const canvasWidth = drawSizeX * shreadSize;// + (pixelX % this.tileSize) * shreadSize; + const canvasHeight = drawSizeY * shreadSize;// + (pixelY % this.tileSize) * shreadSize; canvas.width = canvasWidth; canvas.height = canvasHeight; - console.log( - `Draw X: ${drawSizeX}\nDraw Y: ${drawSizeY}\nCanvas Width: ${canvasWidth}\nCanvas Height: ${canvasHeight}` - ); + console.log(`Draw X: ${drawSizeX}\nDraw Y: ${drawSizeY}\nCanvas Width: ${canvasWidth}\nCanvas Height: ${canvasHeight}`); context.imageSmoothingEnabled = false; // Nearest neighbor - console.log( - `Getting X ${pixelX}-${pixelX + drawSizeX}\nGetting Y ${pixelY}-${ - pixelY + drawSizeY - }` - ); + console.log(`Getting X ${pixelX}-${pixelX + drawSizeX}\nGetting Y ${pixelY}-${pixelY + drawSizeY}`); // Draws the template segment on this tile segment context.clearRect(0, 0, canvasWidth, canvasHeight); // Clear any previous drawing (only runs when canvas size does not change) context.drawImage( - bitmap, - pixelX - this.coords[2], - pixelY - this.coords[3], - drawSizeX, - drawSizeY, - (pixelX % this.tileSize) * shreadSize, - (pixelY % this.tileSize) * shreadSize, - drawSizeX * shreadSize, - drawSizeY * shreadSize + bitmap, // Bitmap image to draw + pixelX - this.coords[2], // Coordinate X to draw from + pixelY - this.coords[3], // Coordinate Y to draw from + drawSizeX, // X width to draw from + drawSizeY, // Y height to draw from + 0, // Coordinate X to draw at + 0, // Coordinate Y to draw at + drawSizeX * shreadSize, // X width to draw at + drawSizeY * shreadSize // Y height to draw at ); // Coordinates and size of draw area of source image, then canvas // const final = await canvas.convertToBlob({ type: 'image/png' }); @@ -223,7 +145,6 @@ export default class Template { console.log(`Shreaded pixels for ${pixelX}, ${pixelY}`, imageData); context.putImageData(imageData, 0, 0); - //templateTiles[`${(this.coords[0] + Math.floor(pixelX / 1000)).toString().padStart(4, '0')},${(this.coords[1] + Math.floor(pixelY / 1000)).toString().padStart(4, '0')},${(pixelX % 1000).toString().padStart(3, '0')},${(pixelY % 1000).toString().padStart(3, '0')}`] = await canvas.convertToBlob({ type: 'image/png' }); templateTiles[ `${(this.coords[0] + Math.floor(pixelX / 1000)) .toString() @@ -236,11 +157,6 @@ export default class Template { console.log(templateTiles); - // const final = await canvas.convertToBlob({ type: 'image/png' }); - // const url = URL.createObjectURL(final); // Creates a blob URL - // window.open(url, '_blank'); // Opens a new tab with blob - // setTimeout(() => URL.revokeObjectURL(url), 10000); // Destroys the blob 10 seconds later - pixelX += drawSizeX; } diff --git a/src/templateManager.js b/src/templateManager.js index 85651ba..2b6a503 100644 --- a/src/templateManager.js +++ b/src/templateManager.js @@ -1,32 +1,9 @@ import Template from "./Template"; import { numberToEncoded } from "./utils"; -/** Manages the comprehensive template system with integrated pixel counting and statistics. - * - * This class handles all external requests for template modification, creation, and statistical analysis. - * It serves as the central coordinator between template instances and the user interface, providing - * real-time feedback on template statistics including pixel counts and rendering status. - * - * ENHANCED FEATURES: - * - Real-time pixel counting and statistics display - * - Intelligent template filtering based on active tiles - * - Internationalized number formatting for large pixel counts - * - Comprehensive status reporting with detailed template information - * - Enhanced user feedback during template creation and rendering - * - * PIXEL COUNTING SYSTEM: - * The template manager integrates with the Template class pixel counting system to provide: - * - Individual template pixel counts during creation - * - Aggregate pixel counts for multiple templates during rendering - * - Smart filtering to count only actively displayed templates - * - Formatted display of pixel statistics in user interface - * - * STATISTICAL INTEGRATION POINTS: - * 1. Template Creation: Displays pixel count when new templates are processed - * 2. Template Rendering: Shows aggregate pixel count for templates being displayed - * 3. Tile Filtering: Counts pixels only for templates active in current viewport - * 4. User Interface: Provides formatted statistics for status messages - * +/** Manages the template system. + * This class handles all external requests for template modification, creation, and analysis. + * It serves as the central coordinator between template instances and the user interface. * @since 0.55.8 * @example * // JSON structure for a template @@ -205,31 +182,8 @@ export default class TemplateManager { } - /** Draws all templates on the specified tile with intelligent pixel count reporting. - * - * This method handles the rendering of template overlays on individual tiles and provides - * comprehensive statistics about the templates being displayed. It integrates with the - * pixel counting system to give users real-time feedback about template complexity. - * - * PIXEL COUNTING INTEGRATION: - * The method implements intelligent pixel counting that: - * - Identifies templates that have content in the current tile - * - Sums pixel counts only for templates actually being rendered - * - Formats large numbers with locale-appropriate separators - * - Provides detailed status messages with template and pixel statistics - * - * PERFORMANCE OPTIMIZATIONS: - * - Filters templates by tile coordinates before processing - * - Counts pixels only for active templates to avoid unnecessary calculations - * - Uses efficient array operations for template filtering and aggregation - * - Caches formatted numbers to avoid repeated formatting operations - * - * USER EXPERIENCE ENHANCEMENTS: - * - Shows both template count and total pixel count in status messages - * - Uses internationalized number formatting for better readability - * - Provides immediate feedback when templates are being displayed - * - Handles singular/plural forms correctly for template count - * + /** Draws all templates on the specified tile. + * This method handles the rendering of template overlays on individual tiles. * @param {File} tileBlob - The pixels that are placed on a tile * @param {[number, number]} tileCoords - The tile coordinates [x, y] * @since 0.65.77 @@ -246,35 +200,41 @@ export default class TemplateManager { const templateArray = this.templatesArray; // Stores a copy for sorting // Sorts the array of Template class instances. 0 = first = lowest draw priority - templateArray.sort((a, b) => { - return a.sortID - b.sortID; - }); + templateArray.sort((a, b) => {return a.sortID - b.sortID;}); console.log(templateArray); // Retrieves the relavent template tile blobs - const templateBlobs = templateArray + const templatesToDraw = templateArray .map(template => { const matchingTiles = Object.keys(template.chunked).filter(tile => tile.startsWith(tileCoords) ); - if (matchingTiles.length === 0) {return null;} // Return nothing when nothing is found + if (matchingTiles.length === 0) {return null;} // Return null when nothing is found // Retrieves the blobs of the templates for this tile - const matchingTileBlobs = matchingTiles.map(tile => template.chunked[tile]); + const matchingTileBlobs = matchingTiles.map(tile => { + + const coords = tile.split(','); // [x, y, x, y] Tile/pixel coordinates + + return { + bitmap: template.chunked[tile], + tileCoords: [coords[0], coords[1]], + pixelCoords: [coords[2], coords[3]] + } + }); return matchingTileBlobs?.[0]; }) .filter(Boolean); - console.log(templateBlobs); + console.log(templatesToDraw); - if (templateBlobs.length > 0) { - // ==================== INTELLIGENT PIXEL COUNTING SYSTEM ==================== + if (templatesToDraw?.bitmap?.length > 0) { + // Calculate total pixel count for templates actively being displayed in this tile // This provides accurate statistics by counting only templates with content in the current viewport - const totalPixels = templateArray .filter(template => { // Filter templates to include only those with tiles matching current coordinates @@ -293,7 +253,7 @@ export default class TemplateManager { // Display comprehensive status information including both template count and pixel statistics // This gives users immediate feedback about the complexity and scope of what's being rendered this.overlay.handleDisplayStatus( - `Displaying ${templateBlobs.length} template${templateBlobs.length == 1 ? '' : 's'}. ` + + `Displaying ${templatesToDraw.bitmap.length} template${templatesToDraw.bitmap.length == 1 ? '' : 's'}. ` + `Total pixels: ${pixelCountFormatted}` ); } @@ -314,10 +274,12 @@ export default class TemplateManager { context.drawImage(tileBitmap, 0, 0, drawSize, drawSize); // For each template in this tile, draw them. - for (const templateBitmap of templateBlobs) { - console.log(`Template Blob is ${typeof templateBitmap}`); - console.log(templateBitmap); - context.drawImage(templateBitmap, 0, 0); + for (const template of templatesToDraw) { + console.log(`Template:`); + console.log(template); + + // Draws the each template on the tile based on it's relative position + context.drawImage(template.bitmap, Number(template.pixelCoords[0]) * this.drawMult, Number(template.pixelCoords[1]) * this.drawMult); } return await canvas.convertToBlob({ type: 'image/png' });