Please bookmark this page to avoid losing your image tool!

Emoji Video Creator

(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.
async function processImage(originalImg, emojiSetString = "๐Ÿ˜€,๐Ÿ˜,๐Ÿ˜‚,๐Ÿคฃ,๐Ÿ˜ƒ,๐Ÿ˜„,๐Ÿ˜…,๐Ÿ˜†,๐Ÿ˜‰,๐Ÿ˜Š,๐Ÿ˜‹,๐Ÿ˜Ž,๐Ÿ˜,๐Ÿ˜˜,๐Ÿฅฐ,๐Ÿ˜—,๐Ÿ˜™,๐Ÿ˜š,โ˜บ๏ธ,๐Ÿ™‚,๐Ÿค—,๐Ÿคฉ,๐Ÿค”,๐Ÿคจ,๐Ÿ˜,๐Ÿ˜‘,๐Ÿ˜ถ,๐Ÿ™„,๐Ÿ˜,๐Ÿ˜ฃ,๐Ÿ˜ฅ,๐Ÿ˜ฎ,๐Ÿค,๐Ÿ˜ฏ,๐Ÿ˜ช,๐Ÿ˜ซ,๐Ÿ˜ด,๐Ÿ˜Œ,๐Ÿ˜›,๐Ÿ˜œ,๐Ÿ˜,๐Ÿคค,๐Ÿ˜’,๐Ÿ˜“,๐Ÿ˜”,๐Ÿ˜•,๐Ÿ™ƒ,๐Ÿค‘,๐Ÿ˜ฒ,โ˜น๏ธ,๐Ÿ™,๐Ÿ˜–,๐Ÿ˜ž,๐Ÿ˜Ÿ,๐Ÿ˜ค,๐Ÿ˜ข,๐Ÿ˜ญ,๐Ÿ˜ฆ,๐Ÿ˜ง,๐Ÿ˜จ,๐Ÿ˜ฉ,๐Ÿคฏ,๐Ÿ˜ฌ,๐Ÿ˜ฐ,๐Ÿ˜ฑ,๐Ÿฅต,๐Ÿฅถ,๐Ÿ˜ณ,๐Ÿคช,๐Ÿ˜ต,๐Ÿ˜ก,๐Ÿ˜ ,๐Ÿคฌ,๐Ÿ˜ท,๐Ÿค’,๐Ÿค•,๐Ÿคข,๐Ÿคฎ,๐Ÿคง,๐Ÿ˜‡,๐Ÿค ,๐Ÿคก,๐Ÿฅณ,๐Ÿฅด,๐Ÿฅบ,๐Ÿคฅ,๐Ÿคซ,๐Ÿคญ,๐Ÿง,๐Ÿค“,๐Ÿ˜ˆ,๐Ÿ‘ฟ,๐Ÿ‘น,๐Ÿ‘บ,๐Ÿ’€,๐Ÿ‘ป,๐Ÿ‘ฝ,๐Ÿ‘พ,๐Ÿค–,๐Ÿ’ฉ,๐Ÿ˜บ,๐Ÿ˜ธ,๐Ÿ˜น,๐Ÿ˜ป,๐Ÿ˜ผ,๐Ÿ˜ฝ,๐Ÿ™€,๐Ÿ˜ฟ,๐Ÿ˜พ", blockSize = "15", animationSpeed = "1", maxDimension = "800") {
    
    const size = Math.max(5, parseInt(blockSize) || 15);
    const speed = parseFloat(animationSpeed) || 1;
    const maxDim = parseInt(maxDimension) || 800;

    let outWidth = originalImg.width;
    let outHeight = originalImg.height;

    // Scale down if necessary to ensure smooth real-time performance
    if (outWidth > maxDim || outHeight > maxDim) {
        const ratio = Math.min(maxDim / outWidth, maxDim / outHeight);
        outWidth = Math.max(1, Math.floor(outWidth * ratio));
        outHeight = Math.max(1, Math.floor(outHeight * ratio));
    }

    // Build the UI Wrapper
    const wrapper = document.createElement('div');
    wrapper.style.display = "flex";
    wrapper.style.flexDirection = "column";
    wrapper.style.alignItems = "center";
    wrapper.style.gap = "15px";
    wrapper.style.padding = "20px";
    wrapper.style.backgroundColor = "#111";
    wrapper.style.borderRadius = "12px";
    wrapper.style.color = "#eee";
    wrapper.style.fontFamily = "system-ui, sans-serif";
    wrapper.style.width = "100%";
    wrapper.style.boxSizing = "border-box";

    const title = document.createElement('h2');
    title.innerText = "Emoji Video Creator";
    title.style.margin = "0";
    title.style.fontSize = "1.5rem";
    wrapper.appendChild(title);

    const desc = document.createElement('p');
    desc.innerText = "Previewing your animating emoji mosaic. Record a short clip below.";
    desc.style.margin = "0";
    desc.style.fontSize = "0.9rem";
    desc.style.color = "#aaa";
    wrapper.appendChild(desc);

    const canvas = document.createElement('canvas');
    canvas.width = outWidth;
    canvas.height = outHeight;
    canvas.style.maxWidth = "100%";
    canvas.style.maxHeight = "55vh";
    canvas.style.border = "1px solid #333";
    canvas.style.boxShadow = "0 6px 16px rgba(0,0,0,0.6)";
    canvas.style.backgroundColor = "#000";
    wrapper.appendChild(canvas);

    const btnContainer = document.createElement('div');
    btnContainer.style.display = "flex";
    btnContainer.style.alignItems = "center";
    btnContainer.style.gap = "15px";
    
    const recordBtn = document.createElement('button');
    recordBtn.innerText = "โบ Record 3s Video";
    recordBtn.style.padding = "10px 20px";
    recordBtn.style.border = "none";
    recordBtn.style.borderRadius = "30px";
    recordBtn.style.background = "linear-gradient(135deg, #FF4500, #FF0055)";
    recordBtn.style.color = "#fff";
    recordBtn.style.cursor = "pointer";
    recordBtn.style.fontWeight = "bold";
    recordBtn.style.fontSize = "1rem";
    recordBtn.style.transition = "transform 0.2s, opacity 0.2s";

    recordBtn.onmouseover = () => recordBtn.style.transform = "scale(1.05)";
    recordBtn.onmouseout = () => recordBtn.style.transform = "scale(1)";

    const statusText = document.createElement('span');
    statusText.style.fontSize = "0.9rem";
    statusText.style.color = "#aaa";

    btnContainer.appendChild(recordBtn);
    btnContainer.appendChild(statusText);
    wrapper.appendChild(btnContainer);

    // Prepare offscreen canvas to process original image pixels safely
    const offCanvas = document.createElement('canvas');
    offCanvas.width = outWidth;
    offCanvas.height = outHeight;
    const offCtx = offCanvas.getContext('2d', { willReadFrequently: true });

    // Extract image data with actual transparency intact
    offCtx.drawImage(originalImg, 0, 0, outWidth, outHeight);
    const imgData = offCtx.getImageData(0, 0, outWidth, outHeight).data;

    // Prep the offscreen canvas for color blending later (replace transparent background with black)
    offCtx.globalCompositeOperation = "destination-over";
    offCtx.fillStyle = "black";
    offCtx.fillRect(0, 0, outWidth, outHeight);
    offCtx.globalCompositeOperation = "source-over"; // reset

    // Parse the emoji set
    let emojis = [];
    if (emojiSetString.includes(',')) {
        emojis = emojiSetString.split(',').map(e => e.trim()).filter(e => e.length > 0);
    } else {
        emojis = Array.from(emojiSetString.replace(/\s+/g, '')).filter(e => e.length > 0);
    }
    if(emojis.length === 0) emojis = ["๐Ÿ˜€"];

    // Evaluate emojis to pixel blocks
    const blocks = [];
    for (let y = 0; y < outHeight; y += size) {
        for (let x = 0; x < outWidth; x += size) {
            let r = 0, g = 0, b = 0, a = 0, count = 0;
            for (let dy = 0; dy < size; dy++) {
                for (let dx = 0; dx < size; dx++) {
                    let px = x + dx;
                    let py = y + dy;
                    if (px < outWidth && py < outHeight) {
                        let idx = (py * outWidth + px) * 4;
                        r += imgData[idx];
                        g += imgData[idx + 1];
                        b += imgData[idx + 2];
                        a += imgData[idx + 3];
                        count++;
                    }
                }
            }
            if (count > 0 && (a / count) > 25) { // Ensure block is sufficiently opaque
                r = Math.round(r / count);
                g = Math.round(g / count);
                b = Math.round(b / count);
                
                let lum = 0.299 * r + 0.587 * g + 0.114 * b;
                let emojiIdx = Math.floor((lum / 256) * emojis.length);
                
                blocks.push({
                    x: x + size / 2,
                    y: y + size / 2,
                    char: emojis[emojiIdx]
                });
            }
        }
    }

    const animCtx = canvas.getContext('2d', { alpha: false });
    let isRecording = false;
    let keepAnimating = true;

    // Animation Loop
    function renderFrame(time) {
        animCtx.fillStyle = "#000000";
        animCtx.fillRect(0, 0, outWidth, outHeight);

        animCtx.textAlign = "center";
        animCtx.textBaseline = "middle";
        // Inflate the size slightly so emojis touch closely
        animCtx.font = `${Math.floor(size * 1.2)}px "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif`;

        // 1. Draw base emojis waving
        for (let b of blocks) {
            // Apply geometric wave transformations
            let phase = (b.x / outWidth + b.y / outHeight) * 10 + time * 0.002 * speed;
            let moveScale = 1 + 0.2 * Math.sin(phase);
            let rot = 0.2 * Math.cos(phase * 1.3);
            
            animCtx.save();
            animCtx.translate(b.x, b.y);
            animCtx.rotate(rot);
            animCtx.scale(moveScale, moveScale);
            
            animCtx.fillText(b.char, 0, 0);
            animCtx.restore();
        }

        // 2. Tint emojis using 'color' compositing operation from the actual image colors.
        // This transfers hue/saturation but importantly preserves emoji luminance properties.
        animCtx.globalCompositeOperation = "color";
        animCtx.drawImage(offCanvas, 0, 0);
        animCtx.globalCompositeOperation = "source-over";
        
        if (keepAnimating) {
            requestAnimationFrame(renderFrame);
        }
    }

    requestAnimationFrame(renderFrame);

    // Screen Recording functionality
    recordBtn.addEventListener('click', () => {
        if (isRecording) return;
        isRecording = true;
        
        recordBtn.style.opacity = "0.6";
        recordBtn.style.cursor = "not-allowed";
        recordBtn.innerText = "Recording...";
        statusText.innerText = "Capturing frames...";

        const stream = canvas.captureStream(30);
        let mediaRecorder;
        
        const optionsPriority = [
            { mimeType: 'video/webm; codecs=vp9' },
            { mimeType: 'video/webm' },
            { mimeType: 'video/mp4' }
        ];
        
        for (let opt of optionsPriority) {
            try {
                mediaRecorder = new MediaRecorder(stream, opt);
                break;
            } catch (e) {}
        }
        
        if (!mediaRecorder) mediaRecorder = new MediaRecorder(stream); // native fallback

        const chunks = [];
        mediaRecorder.ondataavailable = (e) => {
            if (e.data.size > 0) chunks.push(e.data);
        };

        mediaRecorder.onstop = () => {
            const mimeType = mediaRecorder.mimeType || 'video/webm';
            const ext = mimeType.includes('mp4') ? 'mp4' : 'webm';
            const blob = new Blob(chunks, { type: mimeType });
            const url = URL.createObjectURL(blob);
            
            const a = document.createElement('a');
            a.style.display = 'none';
            a.href = url;
            a.download = `emoji-video.${ext}`;
            document.body.appendChild(a);
            a.click();
            
            setTimeout(() => {
                document.body.removeChild(a);
                window.URL.revokeObjectURL(url);
            }, 100);
            
            isRecording = false;
            recordBtn.style.opacity = "1";
            recordBtn.style.cursor = "pointer";
            recordBtn.innerText = "โบ Record 3s Video";
            statusText.innerText = "โœ… Download complete!";
            
            setTimeout(() => {
                if (!isRecording) statusText.innerText = "";
            }, 4000);
        };

        mediaRecorder.start();

        // Count down automatically shuts off recording at 3 seconds
        let seconds = 3;
        const interval = setInterval(() => {
            seconds--;
            if (seconds > 0) {
                statusText.innerText = `Capturing frames... ${seconds}s left`;
            } else {
                clearInterval(interval);
                mediaRecorder.stop();
            }
        }, 1000);
    });

    // Cleanup animation loop automatically when navigating away/removing from DOM
    const observer = new MutationObserver((mutations) => {
        if (!document.body.contains(wrapper)) {
            keepAnimating = false;
            observer.disconnect();
        }
    });
    observer.observe(document.body, { childList: true, subtree: true });

    return wrapper;
}

Free Image Tool Creator

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

Description

The Emoji Video Creator transforms your static images into dynamic, animated emoji mosaics. By analyzing the colors and luminance of your uploaded photo, the tool replaces pixels with a customized set of emojis, creating a visually unique pattern. The resulting mosaic features a waving animation effect and can be recorded as a short video clip for social media sharing, digital art projects, or creative messaging. Users can customize the animation speed, the size of the emoji blocks, and the specific emoji set used to build the image.

Leave a Reply

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