function clamp(value,min,max){return Math.min(max,Math.max(min,value));} function stem(name){return name.replace(/\.[^.]+$/,"")||"image";} export async function prepareImageFile(file){ if(!file.type.startsWith("image/"))return file; const url=URL.createObjectURL(file); const image=new Image(); image.src=url; await image.decode(); const dialog=document.createElement("dialog"); dialog.className="image-editor-dialog"; dialog.innerHTML=`
`; document.body.append(dialog); const canvas=dialog.querySelector("canvas"),ctx=canvas.getContext("2d"),stage=dialog.querySelector(".image-crop-stage"),zoomInput=dialog.querySelector("[data-zoom]"),aspectSelect=dialog.querySelector("[data-aspect]"),sizeSelect=dialog.querySelector("[data-size]"); let offsetX=0,offsetY=0,dragging=false,lastX=0,lastY=0,accepted=false; function cropBox(){ const rect=stage.getBoundingClientRect(); let width=Math.max(280,rect.width),height=Math.min(520,Math.max(260,rect.height)); const aspect=aspectSelect.value==="original"?image.naturalWidth/image.naturalHeight:aspectSelect.value==="free"?width/height:Number(aspectSelect.value); if(width/height>aspect)width=height*aspect;else height=width/aspect; return {width:Math.round(width),height:Math.round(height)}; } function draw(){ const box=cropBox(),dpr=Math.min(devicePixelRatio||1,2); canvas.width=Math.round(box.width*dpr);canvas.height=Math.round(box.height*dpr);canvas.style.width=`${box.width}px`;canvas.style.height=`${box.height}px`; ctx.setTransform(dpr,0,0,dpr,0,0);ctx.clearRect(0,0,box.width,box.height); const wholeImage=aspectSelect.value==="original"; zoomInput.disabled=wholeImage; const base=wholeImage?Math.min(box.width/image.naturalWidth,box.height/image.naturalHeight):Math.max(box.width/image.naturalWidth,box.height/image.naturalHeight),scale=base*(wholeImage?1:Number(zoomInput.value)); const drawW=image.naturalWidth*scale,drawH=image.naturalHeight*scale; const maxX=Math.max(0,(drawW-box.width)/2),maxY=Math.max(0,(drawH-box.height)/2); offsetX=clamp(offsetX,-maxX,maxX);offsetY=clamp(offsetY,-maxY,maxY); ctx.drawImage(image,(box.width-drawW)/2+offsetX,(box.height-drawH)/2+offsetY,drawW,drawH); } function point(event){const p=event.touches?.[0]||event;return {x:p.clientX,y:p.clientY};} canvas.addEventListener("pointerdown",event=>{dragging=true;canvas.setPointerCapture(event.pointerId);({x:lastX,y:lastY}=point(event));}); canvas.addEventListener("pointermove",event=>{if(!dragging)return;const p=point(event);offsetX+=p.x-lastX;offsetY+=p.y-lastY;lastX=p.x;lastY=p.y;draw();}); canvas.addEventListener("pointerup",()=>dragging=false);canvas.addEventListener("pointercancel",()=>dragging=false); zoomInput.addEventListener("input",draw);aspectSelect.addEventListener("change",()=>{offsetX=0;offsetY=0;draw();});window.addEventListener("resize",draw,{signal:(()=>{const c=new AbortController();dialog.addEventListener("close",()=>c.abort(),{once:true});return c.signal;})()}); const result=new Promise(resolve=>{ dialog.addEventListener("close",()=>{URL.revokeObjectURL(url);dialog.remove();resolve(accepted);},{once:true}); dialog.querySelector("[data-apply]").addEventListener("click",async()=>{ const box=cropBox(),maxSize=Number(sizeSelect.value),out=document.createElement("canvas"); if(aspectSelect.value==="original"){ const ratio=Math.min(1,maxSize?maxSize/Math.max(image.naturalWidth,image.naturalHeight):1); out.width=Math.max(1,Math.round(image.naturalWidth*ratio));out.height=Math.max(1,Math.round(image.naturalHeight*ratio)); out.getContext("2d").drawImage(image,0,0,out.width,out.height); }else{ const ratio=Math.min(1,maxSize?maxSize/Math.max(box.width,box.height):1); out.width=Math.max(1,Math.round(box.width*ratio));out.height=Math.max(1,Math.round(box.height*ratio)); out.getContext("2d").drawImage(canvas,0,0,out.width,out.height); } const mime=file.type==="image/png"?"image/png":"image/jpeg"; const blob=await new Promise(r=>out.toBlob(r,mime,mime==="image/jpeg"?.88:undefined)); const ext=mime==="image/png"?"png":"jpg"; accepted=new File([blob],`${stem(file.name)}-edited.${ext}`,{type:mime,lastModified:Date.now()}); dialog.close(); }); }); dialog.showModal();requestAnimationFrame(draw); return result; }