diff --git a/wled00/data/pixelforge/pixelforge.htm b/wled00/data/pixelforge/pixelforge.htm index 86a1449754..75adf02a7c 100644 --- a/wled00/data/pixelforge/pixelforge.htm +++ b/wled00/data/pixelforge/pixelforge.htm @@ -25,13 +25,13 @@ } /* shimmer text animation */ .title .sh { - background: linear-gradient(90deg, - #7b47db 0%, #ff6b6b 20%, #feca57 40%, #48dbfb 60%, #7b47db 100%); - background-size: 200% 100%; - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - animation: shimmer 4s ease-in-out 5; - font-size: 36px; + background: linear-gradient(90deg, + #7b47db 0%, #ff6b6b 20%, #feca57 40%, #48dbfb 60%, #7b47db 100%); + background-size: 200% 100%; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + animation: shimmer 4s ease-in-out 5; + font-size: 36px; } @keyframes shimmer { 50% { background-position: 600% 0; } } @@ -278,11 +278,15 @@

Crop & Adjust Image

+
+ + +
- +
Preview at target resolution @@ -430,9 +434,11 @@

PIXEL MAGIC Tool

/* canvases */ const cv=gId('cv'),cx=cv.getContext('2d',{willReadFrequently:true}); const pv=gId('pv'),pvx=pv.getContext('2d',{willReadFrequently:true}); +const rv = cE('canvas'), rvc = rv.getContext('2d',{willReadFrequently:true}); // off screen canvas for drawing resized & rotated image +rv.width = cv.width; rv.height = cv.height; /* globals */ -let wu='',sI=null,sF=null,cI=null,bS=1,iS=1,pX=0,pY=0; +let wu='',sI=null,sF=null,cI=null,bS=1,iS=1,pX=0,pY=0,rot=0; let cr={x:50,y:50,w:200,h:150},drag=false,dH=null,oX=0,oY=0; let pan=false,psX=0,psY=0,poX=0,poY=0; let iL=[]; // image list @@ -774,6 +780,16 @@

PIXEL MAGIC Tool

crClamp(); crDraw(); }; +/* rotation */ +function rotUpd(v){ + if(gId('snap').checked) v = Math.round(v/15)*15 % 360; // snap to multiples of 15° + rot = v; + gId('rotVal').textContent = v; + if(cI) crDraw(); +} +gId('rotSl').oninput = ()=> rotUpd(+gId('rotSl').value); + + /* color change */ gId('bg').oninput=crDraw; @@ -882,12 +898,25 @@

PIXEL MAGIC Tool

/* draw + preview */ function crDraw(){ + if(!cI) return; + + // render rotated image to offscreen + rvc.clearRect(0,0,rv.width,rv.height); + rvc.fillStyle = gId('bg').value; + rvc.fillRect(0,0,rv.width,rv.height); + rvc.imageSmoothingEnabled = false; + rvc.save(); + const dw = cI.width * iS, dh = cI.height * iS; + rvc.translate(pX + dw/2, pY + dh/2); + rvc.rotate(rot * Math.PI / 180); + rvc.drawImage(cI, -dw/2, -dh/2, dw, dh); + rvc.restore(); + + // copy offscreen to visible cx.clearRect(0,0,cv.width,cv.height); - if(!cI)return; - cx.fillStyle=gId('bg').value; cx.fillRect(0,0,cv.width,cv.height); - cx.imageSmoothingEnabled=false; - cx.drawImage(cI,0,0,cI.width,cI.height,pX,pY,cI.width*iS,cI.height*iS); - /* crop frame */ + cx.drawImage(rv, 0, 0); + + // overlay crop frame (only on visible) cx.lineWidth=3; cx.setLineDash([6,4]); cx.shadowColor="#000"; cx.shadowBlur=2; cx.strokeStyle="#FFF"; cx.beginPath(); cx.roundRect(cr.x,cr.y,cr.w,cr.h,6); cx.stroke(); cx.shadowColor="#000F"; @@ -913,7 +942,8 @@

PIXEL MAGIC Tool

const tcx = tc.getContext('2d'); tcx.fillStyle=gId('bg').value; tcx.fillRect(0,0,w,h); // fill background (for transparent images) - tcx.drawImage(cI,(cr.x-pX)/iS,(cr.y-pY)/iS,cr.w/iS,cr.h/iS,0,0,w,h); + tcx.imageSmoothingEnabled = false; + tcx.drawImage(rv, cr.x, cr.y, cr.w, cr.h, 0, 0, w, h); // sample cropped area from off screen canvas blackTh(tcx); // scale/stretch to preview canvas, limit to 256px in largest dimension but keep aspect ratio const ratio = h/w; @@ -1003,11 +1033,28 @@

PIXEL MAGIC Tool

const frames = []; for (let i = 0; i < gF.length; i++) { + // put current GIF frame into tc const id = new ImageData(new Uint8ClampedArray(gF[i].pixels), gI.width, gI.height); tctx.putImageData(id, 0, 0); + + // render this frame into the offscreen rotated canvas (no overlay) + rvc.clearRect(0, 0, rv.width, rv.height); + rvc.fillStyle = gId('bg').value; + rvc.fillRect(0, 0, rv.width, rv.height); + rvc.imageSmoothingEnabled = false; + rvc.save(); + const dw = gI.width * iS, dh = gI.height * iS; + rvc.translate(pX + dw / 2, pY + dh / 2); + rvc.rotate(rot * Math.PI / 180); + rvc.drawImage(tc, -dw / 2, -dh / 2, dw, dh); + rvc.restore(); + + // sample the crop from the offscreen (already rotated) canvas into output size cctx.fillStyle = gId('bg').value; cctx.fillRect(0, 0, w, h); - cctx.drawImage(tc, (cr.x - pX) / iS, (cr.y - pY) / iS, cr.w / iS, cr.h / iS, 0, 0, w, h); + cctx.imageSmoothingEnabled = false; + cctx.drawImage(rv, cr.x, cr.y, cr.w, cr.h, 0, 0, w, h); + blackTh(cctx); const fd = cctx.getImageData(0, 0, w, h); frames.push({ data: fd.data, delay: gF[i].delay });