Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 63 additions & 16 deletions wled00/data/pixelforge/pixelforge.htm
Original file line number Diff line number Diff line change
Expand Up @@ -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; } }

Expand Down Expand Up @@ -278,11 +278,15 @@ <h3 style="margin-top:0;padding-top:0;border-top:0">Crop & Adjust Image</h3>

<div class="cw">
<div style="width:100%">
<div class="slc">
<label>Rotation: <span id="rotVal">0</span>° <input type="checkbox" id="snap">snap</label>
<input type="range" id="rotSl" min="0" max="359" value="0" class="sl">
</div>
<div class="slc">
<label>Zoom: </label>
<input type="range" id="zoom" min="0" max="100" value="0" class="sl">
</div>
<canvas id="cv" width="500" height="400"></canvas>
<canvas id="cv" width="500" height="500"></canvas>
</div>

<small>Preview at target resolution</small>
Expand Down Expand Up @@ -430,9 +434,11 @@ <h3>PIXEL MAGIC Tool</h3>
/* 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
Expand Down Expand Up @@ -774,6 +780,16 @@ <h3>PIXEL MAGIC Tool</h3>
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;

Expand Down Expand Up @@ -882,12 +898,25 @@ <h3>PIXEL MAGIC Tool</h3>

/* 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";
Expand All @@ -913,7 +942,8 @@ <h3>PIXEL MAGIC Tool</h3>
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;
Expand Down Expand Up @@ -1003,11 +1033,28 @@ <h3>PIXEL MAGIC Tool</h3>

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 });
Expand Down