Please bookmark this page to avoid losing your image tool!

Photo To Video Player

(Free & Supports Bulk Upload)

Drag & drop your images here or

The result will appear here...
You can edit the below JavaScript code to customize the image tool.
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;
}

Free Image Tool Creator

Can't find the image tool you're looking for?
Create one based on your own needs now!

Description

The Photo To Video Player tool transforms static images into an interactive, video-like experience. It applies a dynamic pan and zoom effect (Ken Burns effect) to give the image a sense of motion over a customizable duration. The tool features a built-in playback interface, including a play/pause button, a progress scrubber, and a time display, allowing users to interactively view the animated image. This tool is ideal for creating engaging digital presentations, social media previews, or adding life to still photography in web-based environments.

Leave a Reply

Your email address will not be published. Required fields are marked *