new functions and js v=
This commit is contained in:
+51
-51
@@ -1,16 +1,16 @@
|
||||
function clamp(value,min,max){return Math.min(max,Math.max(min,value));}
|
||||
function stem(name){return name.replace(/\.[^.]+$/,"")||"image";}
|
||||
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;
|
||||
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=`<form method="dialog" class="image-editor-panel">
|
||||
const dialog = document.createElement("dialog");
|
||||
dialog.className = "image-editor-dialog";
|
||||
dialog.innerHTML = `<form method="dialog" class="image-editor-panel">
|
||||
<div class="image-editor-head"><div><h2>Adjust image</h2><p>Keep the whole image or choose a crop, then select output size.</p></div><button class="icon-button" value="cancel" aria-label="Close">×</button></div>
|
||||
<div class="image-crop-stage"><canvas></canvas></div>
|
||||
<div class="image-editor-controls">
|
||||
@@ -21,54 +21,54 @@ export async function prepareImageFile(file){
|
||||
<div class="image-editor-actions"><button class="secondary-button" value="cancel">Cancel</button><button type="button" class="primary-button" data-apply>Use image</button></div>
|
||||
</form>`;
|
||||
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;
|
||||
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 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 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;})()});
|
||||
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 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()});
|
||||
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);
|
||||
dialog.showModal(); requestAnimationFrame(draw);
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user