You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, videoDurationSeconds = "10", themeColor = "#e50914", enablePanZoom = "true") {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Ensure the canvas matches the original image dimensions
canvas.width = originalImg.width;
canvas.height = originalImg.height;
// Parse parameters
const durationMs = (parseFloat(videoDurationSeconds) || 10) * 1000;
const panZoom = enablePanZoom === "true";
// State variables
let isPlaying = false;
let progress = 0; // Ranges from 0 to 1
let lastTimestamp = 0;
let isHovering = false;
let lastMouseMovedTime = 0;
let mouseX = 0;
let mouseY = 0;
let isDraggingScrubber = false;
// UI geometry constants
const bottomBarHeight = Math.max(45, canvas.height * 0.08);
const scrubStartX = 65;
const rightMargin = 85;
function draw(timestamp) {
if (!lastTimestamp) lastTimestamp = timestamp;
// Cap delta to prevent massive jumps if tab was in background
const delta = Math.min(timestamp - lastTimestamp, 200);
lastTimestamp = timestamp;
if (isPlaying && !isDraggingScrubber) {
progress += delta / durationMs;
if (progress >= 1) {
progress = 1;
isPlaying = false; // Stop at the end
}
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.beginPath();
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.clip();
// 1. Draw the image (with or without Ken Burns pan/zoom effect)
if (panZoom) {
const scale = 1 + (0.1 * progress);
const dw = canvas.width * scale;
const dh = canvas.height * scale;
const dx = (canvas.width - dw) / 2;
const dy = (canvas.height - dh) / 2;
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
ctx.drawImage(originalImg, dx, dy, dw, dh);
} else {
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
}
// 2. Determine if Player UI should be shown
const timeSinceMove = Date.now() - lastMouseMovedTime;
const showUI = !isPlaying || isDraggingScrubber || (isHovering && timeSinceMove < 2500);
if (showUI) {
// Big central Play Button and darken overlay if paused
if (!isPlaying) {
// Dimmer background to make play button pop
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const btnRadius = Math.min(canvas.width, canvas.height) * 0.1;
ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
ctx.beginPath();
ctx.arc(canvas.width / 2, canvas.height / 2, btnRadius, 0, Math.PI * 2);
ctx.fill();
// Play triangle
ctx.fillStyle = 'white';
ctx.beginPath();
const triSize = btnRadius * 0.45;
ctx.moveTo(canvas.width / 2 - triSize * 0.5, canvas.height / 2 - triSize * 0.866);
ctx.lineTo(canvas.width / 2 + triSize, canvas.height / 2);
ctx.lineTo(canvas.width / 2 - triSize * 0.5, canvas.height / 2 + triSize * 0.866);
ctx.closePath();
ctx.fill();
}
// Bottom control bar background gradient
const grad = ctx.createLinearGradient(0, canvas.height - bottomBarHeight * 1.5, 0, canvas.height);
grad.addColorStop(0, 'rgba(0,0,0,0)');
grad.addColorStop(1, 'rgba(0,0,0,0.8)');
ctx.fillStyle = grad;
ctx.fillRect(0, canvas.height - bottomBarHeight * 1.5, canvas.width, bottomBarHeight * 1.5);
// Bottom play/pause switch
ctx.fillStyle = 'white';
const btnCenterY = canvas.height - bottomBarHeight / 2;
const btnCenterX = 30;
const s = Math.min(12, bottomBarHeight * 0.25);
if (isPlaying) {
// Pause icon
ctx.fillRect(btnCenterX - s * 0.7, btnCenterY - s * 0.8, s * 0.5, s * 1.6);
ctx.fillRect(btnCenterX + s * 0.2, btnCenterY - s * 0.8, s * 0.5, s * 1.6);
} else {
// Play icon
ctx.beginPath();
ctx.moveTo(btnCenterX - s * 0.5, btnCenterY - s * 0.866);
ctx.lineTo(btnCenterX + s, btnCenterY);
ctx.lineTo(btnCenterX - s * 0.5, btnCenterY + s * 0.866);
ctx.closePath();
ctx.fill();
}
// Scrubber line background
const scrubEndX = canvas.width - rightMargin;
const scrubY = btnCenterY;
ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)';
ctx.lineWidth = 4;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(scrubStartX, scrubY);
ctx.lineTo(scrubEndX, scrubY);
ctx.stroke();
// Scrubber progress fill
const scrubCurrentX = scrubStartX + (scrubEndX - scrubStartX) * progress;
if (progress > 0) {
ctx.strokeStyle = themeColor;
ctx.beginPath();
ctx.moveTo(scrubStartX, scrubY);
ctx.lineTo(scrubCurrentX, scrubY);
ctx.stroke();
}
// Scrubber handle dot (gets larger when dragged/hovered)
const overScrubber = mouseY > canvas.height - bottomBarHeight && mouseX >= scrubStartX && mouseX <= scrubEndX;
const dotRadius = (isDraggingScrubber || overScrubber) ? 7 : 5;
ctx.fillStyle = themeColor;
ctx.beginPath();
ctx.arc(scrubCurrentX, scrubY, dotRadius, 0, Math.PI * 2);
ctx.fill();
// Timer text
ctx.fillStyle = 'white';
ctx.font = `bold ${Math.max(11, bottomBarHeight * 0.3)}px sans-serif`;
ctx.textAlign = 'right';
ctx.textBaseline = 'middle';
const currSec = Math.floor((progress * durationMs) / 1000);
const durSec = Math.floor(durationMs / 1000);
const formatTime = (secs) => {
const m = Math.floor(secs / 60);
const secsStr = secs % 60;
return `${m}:${secsStr.toString().padStart(2, '0')}`;
};
ctx.fillText(`${formatTime(currSec)} / ${formatTime(durSec)}`, canvas.width - 15, btnCenterY);
}
ctx.restore();
// Loop the animation
requestAnimationFrame(draw);
}
// Kick off animation loop
requestAnimationFrame((ts) => {
lastTimestamp = ts;
requestAnimationFrame(draw);
});
const updateProgressFromMouse = () => {
const scrubEndX = canvas.width - rightMargin;
let p = (mouseX - scrubStartX) / (scrubEndX - scrubStartX);
p = Math.max(0, Math.min(1, p));
progress = p;
};
// --- Interactive Event Listeners ---
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
// Scale mouse coords based on canvas size vs display size
mouseX = (e.clientX - rect.left) * (canvas.width / rect.width);
mouseY = (e.clientY - rect.top) * (canvas.height / rect.height);
isHovering = true;
lastMouseMovedTime = Date.now();
if (isDraggingScrubber) {
updateProgressFromMouse();
}
// Change cursor to pointer for interactive elements
const overScrub = mouseY > canvas.height - bottomBarHeight && mouseX >= scrubStartX && mouseX <= canvas.width - rightMargin;
const overCenterPlay = !isPlaying && Math.hypot(mouseX - canvas.width/2, mouseY - canvas.height/2) < Math.min(canvas.width, canvas.height) * 0.1;
const overBottomPlay = mouseY > canvas.height - bottomBarHeight && mouseX < scrubStartX;
if (overScrub || overCenterPlay || overBottomPlay) {
canvas.style.cursor = 'pointer';
} else {
canvas.style.cursor = 'default';
}
});
canvas.addEventListener('mouseenter', () => {
isHovering = true;
lastMouseMovedTime = Date.now();
});
canvas.addEventListener('mouseleave', () => {
isHovering = false;
isDraggingScrubber = false;
canvas.style.cursor = 'default';
});
canvas.addEventListener('mousedown', (e) => {
if (!isHovering) return;
const scrubEndX = canvas.width - rightMargin;
// Detect scrubber click
if (mouseY > canvas.height - bottomBarHeight && mouseX >= scrubStartX - 10 && mouseX <= scrubEndX + 10) {
isDraggingScrubber = true;
updateProgressFromMouse();
return;
}
// Play/Pause toggle interaction across the rest of the canvas
if (progress >= 1) {
progress = 0;
isPlaying = true; // Restart if at the end
} else {
isPlaying = !isPlaying;
}
});
canvas.addEventListener('mouseup', () => {
isDraggingScrubber = false;
});
// Prevent text selection cursor during fast interaction scrub/dragging
canvas.onselectstart = () => false;
return canvas;
}
Apply Changes