You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, spinSpeed = 4, showStick = 1) {
const speed = isNaN(parseFloat(spinSpeed)) ? 4 : parseFloat(spinSpeed);
const stickVisible = parseInt(showStick, 10) !== 0;
const maxCanvasSize = 800; // Cap resolution
let size = Math.min(originalImg.width, originalImg.height);
if (size > maxCanvasSize) {
size = maxCanvasSize;
}
const R = size / 2;
// Center crop the original image
const sqCanvas = document.createElement("canvas");
sqCanvas.width = size;
sqCanvas.height = size;
const sqCtx = sqCanvas.getContext("2d");
const minDim = Math.min(originalImg.width, originalImg.height);
const sx = (originalImg.width - minDim) / 2;
const sy = (originalImg.height - minDim) / 2;
sqCtx.imageSmoothingEnabled = true;
sqCtx.imageSmoothingQuality = "high";
sqCtx.drawImage(originalImg, sx, sy, minDim, minDim, 0, 0, size, size);
const img = sqCanvas;
const canvasW = 2.4 * R;
const canvasH = stickVisible ? 3.4 * R : 2.4 * R;
const cx = canvasW / 2;
const cy = 1.2 * R;
// Create main responsive wrapper DOM
const wrapper = document.createElement("div");
wrapper.style.position = "relative";
wrapper.style.width = canvasW + "px";
wrapper.style.height = canvasH + "px";
wrapper.style.display = "inline-block";
function createLayer() {
const cvs = document.createElement("canvas");
cvs.width = canvasW;
cvs.height = canvasH;
cvs.style.position = "absolute";
cvs.style.top = "0";
cvs.style.left = "0";
return cvs;
}
// 1. Draw static background stick
if (stickVisible) {
const stickCanvas = createLayer();
const sCtx = stickCanvas.getContext("2d");
sCtx.translate(cx, cy);
const stickW = R * 0.08;
const stickL = R * 2.2;
const stickGrad = sCtx.createLinearGradient(-stickW/2, 0, stickW/2, 0);
stickGrad.addColorStop(0, "#4a2e1b");
stickGrad.addColorStop(0.3, "#CD853F");
stickGrad.addColorStop(0.7, "#DEB887");
stickGrad.addColorStop(1, "#8B4513");
sCtx.fillStyle = stickGrad;
sCtx.fillRect(-stickW/2, 0, stickW, stickL);
const stickShadow = sCtx.createLinearGradient(0, 0, 0, R * 0.4);
stickShadow.addColorStop(0, "rgba(0,0,0,0.7)");
stickShadow.addColorStop(1, "transparent");
sCtx.fillStyle = stickShadow;
sCtx.fillRect(-stickW/2, 0, stickW, stickL);
wrapper.appendChild(stickCanvas);
}
// 2. Spinner Container (Applies static drop shadow while content rotates)
const spinnerDiv = document.createElement("div");
spinnerDiv.style.position = "absolute";
spinnerDiv.style.top = "0";
spinnerDiv.style.left = "0";
spinnerDiv.style.width = "100%";
spinnerDiv.style.height = "100%";
spinnerDiv.style.filter = `drop-shadow(${R*0.04}px ${R*0.06}px ${R*0.05}px rgba(0,0,0,0.5))`;
// 3. Draw the Pinwheel
const wheelCanvas = createLayer();
const wCtx = wheelCanvas.getContext("2d");
wCtx.translate(cx, cy);
wCtx.imageSmoothingEnabled = true;
wCtx.imageSmoothingQuality = "high";
// Draw the 4 flat base cuts of the folded paper
for (let i = 0; i < 4; i++) {
wCtx.save();
wCtx.rotate((Math.PI / 2) * i);
wCtx.beginPath();
wCtx.moveTo(0, 0);
wCtx.lineTo(-R, -R);
wCtx.lineTo(0, -R);
wCtx.lineTo(R / 2, -R / 2);
wCtx.closePath();
wCtx.clip();
wCtx.drawImage(img, -R, -R, 2 * R, 2 * R);
wCtx.restore();
}
// Draw the 4 overlapping folded flaps with 3D internal shadows
for (let i = 0; i < 4; i++) {
wCtx.save();
const A = (Math.PI / 2) * i;
wCtx.rotate(A);
wCtx.beginPath();
wCtx.moveTo(0, 0);
wCtx.lineTo(0, -R);
wCtx.lineTo(R / 2, -R / 2);
wCtx.closePath();
wCtx.save();
wCtx.shadowColor = "rgba(0,0,0,0.7)";
wCtx.shadowBlur = R * 0.08;
// Dynamically rotate un-transformed shadow constants so shadow logic remains strictly bound to wheel axes
const dx = R * 0.03;
const dy = R * 0.03;
wCtx.shadowOffsetX = dx * Math.cos(A) - dy * Math.sin(A);
wCtx.shadowOffsetY = dx * Math.sin(A) + dy * Math.cos(A);
wCtx.fillStyle = "white";
wCtx.fill();
wCtx.restore();
wCtx.clip();
// Mirrors image over the folding crease exactly simulating paper reversing back-side
wCtx.transform(0, 1, 1, 0, R, -R);
wCtx.drawImage(img, -R, -R, 2 * R, 2 * R);
wCtx.fillStyle = "rgba(0,0,0,0.2)";
wCtx.fillRect(-R, -R, 2 * R, 2 * R);
wCtx.restore();
}
// Animate the rotation
if (speed > 0) {
const animName = "spin-" + Math.random().toString(36).substr(2, 6);
const style = document.createElement("style");
style.textContent = `
@keyframes ${animName} {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
`;
document.head.appendChild(style);
wheelCanvas.style.transformOrigin = `${(cx / canvasW) * 100}% ${(cy / canvasH) * 100}%`;
wheelCanvas.style.animation = `${animName} ${speed}s linear infinite`;
}
spinnerDiv.appendChild(wheelCanvas);
wrapper.appendChild(spinnerDiv);
// 4. Draw Center Lock Pin Cap (Stays fixed relative to pinwheel rotation)
const pinCanvas = createLayer();
const pCtx = pinCanvas.getContext("2d");
pCtx.translate(cx, cy);
pCtx.beginPath();
pCtx.arc(0, 0, R * 0.06, 0, Math.PI * 2);
const pinGrad = pCtx.createRadialGradient(R*0.015, -R*0.015, 0, 0, 0, R*0.06);
pinGrad.addColorStop(0, "#FFFACD");
pinGrad.addColorStop(0.3, "#FFD700");
pinGrad.addColorStop(0.8, "#B8860B");
pinGrad.addColorStop(1, "#8B6508");
pCtx.fillStyle = pinGrad;
pCtx.fill();
pCtx.beginPath();
pCtx.arc(0, 0, R * 0.06, 0, Math.PI * 2);
pCtx.lineWidth = Math.max(1, R * 0.01);
pCtx.strokeStyle = "rgba(0,0,0,0.5)";
pCtx.stroke();
wrapper.appendChild(pinCanvas);
return wrapper;
}
Apply Changes