You can edit the below JavaScript code to customize the image tool.
Apply Changes
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;
}
Apply Changes