Please bookmark this page to avoid losing your image tool!

Image To Art Video Generator

(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, particleCount = "2500", speed = "2", strokeSize = "1.5", fadeSpeed = "0.05", recordDurationSeconds = "5") {
    // Parse parameters
    const pCount = parseInt(particleCount);
    const spd = parseFloat(speed);
    const sSize = parseFloat(strokeSize);
    const fSpeed = parseFloat(fadeSpeed);

    // Setup main container UI
    const container = document.createElement('div');
    container.style.position = 'relative';
    container.style.display = 'inline-flex';
    container.style.flexDirection = 'column';
    container.style.alignItems = 'center';
    container.style.gap = '15px';
    container.style.fontFamily = 'sans-serif';
    container.style.background = '#1a1a1a';
    container.style.padding = '20px';
    container.style.borderRadius = '12px';
    container.style.maxWidth = '100%';
    container.style.boxSizing = 'border-box';

    // Responsively scale original image for optimal performance
    let w = originalImg.width;
    let h = originalImg.height;
    const MAX_DIM = 1200;
    if (w > MAX_DIM || h > MAX_DIM) {
        if (w > h) {
            h = Math.floor(h * (MAX_DIM / w));
            w = MAX_DIM;
        } else {
            w = Math.floor(w * (MAX_DIM / h));
            h = MAX_DIM;
        }
    }

    const wrapper = document.createElement('div');
    wrapper.style.position = 'relative';
    wrapper.style.display = 'inline-block';
    wrapper.style.maxWidth = '100%';
    wrapper.style.boxShadow = '0 6px 18px rgba(0,0,0,0.6)';
    wrapper.style.borderRadius = '8px';
    wrapper.style.overflow = 'hidden';

    // Setup rendering canvas
    const canvas = document.createElement('canvas');
    canvas.width = w;
    canvas.height = h;
    canvas.style.maxWidth = '100%';
    canvas.style.display = 'block';
    const ctx = canvas.getContext('2d', { alpha: false }); // performance optimization

    wrapper.appendChild(canvas);
    container.appendChild(wrapper);

    // Fill screen purely black initially
    ctx.fillStyle = '#000';
    ctx.fillRect(0, 0, w, h);

    // Extract image pixel data using an offscreen canvas
    const offCanvas = document.createElement('canvas');
    offCanvas.width = w;
    offCanvas.height = h;
    const offCtx = offCanvas.getContext('2d', { willReadFrequently: true });
    offCtx.drawImage(originalImg, 0, 0, w, h);

    let imgData;
    try {
        imgData = offCtx.getImageData(0, 0, w, h).data;
    } catch(e) {
        container.innerHTML = `<span style="color:#ff6b6b; font-family:sans-serif; text-align:center;">Error: Could not retrieve image data due to CORS policy. Please process a local or cleanly-proxied image.</span>`;
        return container;
    }

    // Precalculate angle variations based on image gradients (generative flow field calculation)
    // This allows particles to aesthetically follow the contours and strokes of the image subject
    const angles = new Float32Array(w * h);
    for (let y = 0; y < h; y++) {
        for (let x = 0; x < w; x++) {
            const px0 = x - 1 < 0 ? 0 : x - 1;
            const px1 = x + 1 >= w ? w - 1 : x + 1;
            const py0 = y - 1 < 0 ? 0 : y - 1;
            const py1 = y + 1 >= h ? h - 1 : y + 1;

            const iU = (py0 * w + x) * 4;
            const iD = (py1 * w + x) * 4;
            const iL = (y * w + px0) * 4;
            const iR = (y * w + px1) * 4;

            const bU = imgData[iU] + imgData[iU+1] + imgData[iU+2];
            const bD = imgData[iD] + imgData[iD+1] + imgData[iD+2];
            const bL = imgData[iL] + imgData[iL+1] + imgData[iL+2];
            const bR = imgData[iR] + imgData[iR+1] + imgData[iR+2];

            const dx = bR - bL;
            const dy = bD - bU;

            let angle;
            if (dx === 0 && dy === 0) {
                // Introduce subtle structured noise in uniform flat areas
                angle = Math.sin(x * 0.05) + Math.cos(y * 0.05);
            } else {
                angle = Math.atan2(dy, dx) + Math.PI / 2;
            }
            angles[y * w + x] = angle;
        }
    }

    // Define generative particle structure modeling stroke brushes
    class Particle {
        constructor() {
            this.reset();
            this.life = Math.random() * 200;
        }
        reset() {
            this.x = Math.random() * w;
            this.y = Math.random() * h;
            this.prevX = this.x;
            this.prevY = this.y;
            this.vx = 0;
            this.vy = 0;
            this.life = Math.random() * 150 + 50;
        }
        update() {
            this.prevX = this.x;
            this.prevY = this.y;

            const ix = Math.floor(this.x);
            const iy = Math.floor(this.y);
            const i = iy * w + ix;
            const angle = angles[i];

            const targetVx = Math.cos(angle) * spd;
            const targetVy = Math.sin(angle) * spd;

            this.vx += (targetVx - this.vx) * 0.1;
            this.vy += (targetVy - this.vy) * 0.1;

            this.x += this.vx;
            this.y += this.vy;

            this.life--;

            if (this.life <= 0 || this.x < 1 || this.x >= w - 1 || this.y < 1 || this.y >= h - 1) {
                this.reset();
                return null;
            }

            const idx = i * 4;
            return {
                r: imgData[idx],
                g: imgData[idx + 1],
                b: imgData[idx + 2]
            };
        }
        draw(ctx, color) {
            ctx.beginPath();
            ctx.moveTo(this.prevX, this.prevY);
            ctx.lineTo(this.x, this.y);
            // Draw with transparency for rich blending overlap 
            ctx.strokeStyle = `rgba(${color.r}, ${color.g}, ${color.b}, 0.7)`;
            ctx.lineWidth = sSize;
            ctx.lineCap = 'round';
            ctx.stroke();
        }
    }

    const particles = [];
    for (let i = 0; i < pCount; i++) {
        particles.push(new Particle());
    }

    let hasBeenConnected = false;
    let animId;
    function animate() {
        // Prevent memory leak by cleaning up rendering if tool is unmounted
        if (!hasBeenConnected) {
            if (container.isConnected) hasBeenConnected = true;
        } else {
            if (!container.isConnected) return;
        }

        // Add a slight fading trail representing motion trace
        ctx.fillStyle = `rgba(0, 0, 0, ${fSpeed})`;
        ctx.fillRect(0, 0, w, h);

        // Update and draw flowing colored painter strokes
        for (let p of particles) {
            const color = p.update();
            if (color) {
                p.draw(ctx, color);
            }
        }

        animId = requestAnimationFrame(animate);
    }
    
    // Quick-seed positions sequentially for an aesthetically faster start
    for (let i = 0; i < 30; i++) { for (let p of particles) p.update(); }
    animate(); // Run artistic animation

    // Set up Native Web Video Recording Utility
    const btnContainer = document.createElement('div');
    btnContainer.style.display = 'flex';
    btnContainer.style.gap = '15px';

    const btnRecord = document.createElement('button');
    btnRecord.innerText = `Record & Download Video (${recordDurationSeconds}s)`;
    btnRecord.style.padding = '12px 24px';
    btnRecord.style.background = '#007BFF';
    btnRecord.style.color = '#fff';
    btnRecord.style.border = 'none';
    btnRecord.style.borderRadius = '24px';
    btnRecord.style.fontSize = '15px';
    btnRecord.style.cursor = 'pointer';
    btnRecord.style.fontWeight = 'bold';
    btnRecord.style.transition = 'all 0.2s ease';
    btnRecord.style.boxShadow = '0 4px 10px rgba(0, 123, 255, 0.4)';

    btnRecord.onmouseenter = () => { if (!btnRecord.disabled) btnRecord.style.background = '#0056b3'; };
    btnRecord.onmouseleave = () => { if (!btnRecord.disabled) btnRecord.style.background = '#007BFF'; };

    // Start video recording action
    btnRecord.onclick = () => {
        if (!canvas.captureStream) {
            alert('Video recording is not supported natively in this browser version.');
            return;
        }
        let options;
        if (MediaRecorder.isTypeSupported('video/webm; codecs=vp9')) {
            options = { mimeType: 'video/webm; codecs=vp9' };
        } else if (MediaRecorder.isTypeSupported('video/webm')) {
            options = { mimeType: 'video/webm' };
        } else if (MediaRecorder.isTypeSupported('video/mp4')) {
            options = { mimeType: 'video/mp4' };
        } else {
            options = {}; 
        }

        try {
            const stream = canvas.captureStream(30); // Capture canvas at 30fps
            const recorder = new MediaRecorder(stream, options);
            const chunks = [];
            
            recorder.ondataavailable = e => {
                if (e.data && e.data.size > 0) chunks.push(e.data);
            };
            recorder.onstop = () => {
                const mimeType = options.mimeType || 'video/webm';
                const blob = new Blob(chunks, { type: mimeType });
                const url = URL.createObjectURL(blob);
                
                const a = document.createElement('a');
                a.href = url;
                const ext = mimeType.includes('mp4') ? 'mp4' : 'webm';
                a.download = `art_painted_video.${ext}`;
                a.click();
                URL.revokeObjectURL(url);
                
                // Revert button styling
                btnRecord.innerText = `Record & Download Video (${recordDurationSeconds}s)`;
                btnRecord.disabled = false;
                btnRecord.style.background = '#007BFF';
                btnRecord.style.cursor = 'pointer';
                btnRecord.style.boxShadow = '0 4px 10px rgba(0, 123, 255, 0.4)';
            };

            recorder.start();
            
            // Set Recording UI
            btnRecord.innerText = 'Recording Video... Please wait';
            btnRecord.disabled = true;
            btnRecord.style.background = '#555';
            btnRecord.style.boxShadow = 'none';
            btnRecord.style.cursor = 'not-allowed';

            // Wait standard duration to halt WebM clip capture
            setTimeout(() => {
                if (recorder.state === 'recording') {
                    recorder.stop();
                }
            }, parseFloat(recordDurationSeconds) * 1000);

        } catch (err) {
            console.error('Recording context initialization error:', err);
            alert('An error occurred while generating the web video output setup.');
            btnRecord.innerText = `Record & Download Video (${recordDurationSeconds}s)`;
            btnRecord.disabled = false;
            btnRecord.style.background = '#007BFF';
            btnRecord.style.cursor = 'pointer';
        }
    };

    btnContainer.appendChild(btnRecord);
    container.appendChild(btnContainer);

    return container;
}

Free Image Tool Creator

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

Description

This tool transforms static images into dynamic, artistic videos using a generative particle system. It analyzes the gradients and contours of your uploaded image to create a flow field, guiding colored particles to move along the image’s natural lines like flowing brushstrokes. Users can customize the visual style through parameters such as particle count, movement speed, stroke size, and fade effects. Once the artistic animation is generated, the tool allows you to record and download the motion as a video file, making it ideal for creating unique social media content, digital art pieces, or dynamic visual backgrounds.

Leave a Reply

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