You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, blockSize = 10) {
// Ensure blockSize is a positive integer
blockSize = Math.max(1, Math.floor(blockSize));
const canvas = document.createElement('canvas');
// Add willReadFrequently hint for potential performance optimization by the browser
const ctx = canvas.getContext('2d', { willReadFrequently: true });
// Use naturalWidth/Height for the image's actual dimensions
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
// If image dimensions are invalid or image not loaded, return an empty/error element
if (imgWidth === 0 || imgHeight === 0) {
const errorOutput = document.createElement('pre');
errorOutput.textContent = "Image has zero dimensions or could not be loaded properly.";
return errorOutput;
}
canvas.width = imgWidth;
canvas.height = imgHeight;
ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
// Define a palette of emojis and their representative RGB colors
// These RGB values are approximate and chosen to give a varied palette.
const emojiPalette = [
{ emoji: '🔴', r: 220, g: 40, b: 40 }, // Red
{ emoji: '🟠', r: 255, g: 150, b: 0 }, // Orange
{ emoji: '🟡', r: 255, g: 235, b: 0 }, // Yellow
{ emoji: '🟢', r: 70, g: 180, b: 70 }, // Green
{ emoji: '🔵', r: 50, g: 100, b: 220 }, // Blue
{ emoji: '🟣', r: 160, g: 60, b: 190 }, // Purple
{ emoji: '🟤', r: 140, g: 70, b: 20 }, // Brown
{ emoji: 'âš«', r: 30, g: 30, b: 30 }, // Near Black
{ emoji: '⚪', r: 240, g: 240, b: 240 }, // Near White
{ emoji: '🩷', r: 255, g: 170, b: 185 }, // Pink
{ emoji: '💚', r: 100, g: 220, b: 100 }, // Light Green (distinct from darker green)
{ emoji: '💙', r: 120, g: 170, b: 250 }, // Light Blue (distinct from darker blue)
{ emoji: '🩶', r: 150, g: 150, b: 150 }, // Gray
];
// Helper function to calculate squared Euclidean distance between two colors
// Squared distance is used for efficiency as we only need to compare distances.
function colorDistanceSquared(r1, g1, b1, r2, g2, b2) {
const dR = r1 - r2;
const dG = g1 - g2;
const dB = b1 - b2;
return dR * dR + dG * dG + dB * dB;
}
const outputPre = document.createElement('pre');
// Style the <pre> element for emoji display
outputPre.style.fontFamily = '"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", "sans-serif"';
outputPre.style.lineHeight = '1'; // For tight vertical packing of emojis
outputPre.style.backgroundColor = 'transparent'; // Ensure it blends with parent background
// Adjust font size based on blockSize, within reasonable limits
// This helps make the emoji characters roughly correspond to the block size visually.
const fontSize = Math.min(Math.max(blockSize * 0.8, 8), 30); // Min 8px, Max 30px font
outputPre.style.fontSize = `${fontSize}px`;
let allImageData;
try {
// Get pixel data for the entire image once for performance
allImageData = ctx.getImageData(0, 0, imgWidth, imgHeight);
} catch (e) {
// This can happen due to cross-origin restrictions if the image source is external
// and the canvas becomes tainted.
console.error("Error getting image data:", e);
outputPre.textContent = "Error: Could not access image pixel data. This might be due to cross-origin restrictions.";
return outputPre;
}
const allPixelData = allImageData.data;
const linesArray = []; // Store lines of emojis to join later (performance for many lines)
// Iterate over the image in blocks of size `blockSize` x `blockSize`
for (let y = 0; y < imgHeight; y += blockSize) {
let currentLineEmojis = '';
for (let x = 0; x < imgWidth; x += blockSize) {
// Determine the actual width and height of the current block (handles edges)
const currentBlockWidth = Math.min(blockSize, imgWidth - x);
const currentBlockHeight = Math.min(blockSize, imgHeight - y);
let sumR = 0, sumG = 0, sumB = 0;
let numPixelsInBlock = 0;
// Calculate the average color of the pixels in the current block
for (let j = 0; j < currentBlockHeight; j++) { // Iterate rows within the block
for (let i = 0; i < currentBlockWidth; i++) { // Iterate columns within the block
const pixelXInImage = x + i;
const pixelYInImage = y + j;
// Calculate the index in the 1D pixel data array
const dataIndex = (pixelYInImage * imgWidth + pixelXInImage) * 4;
sumR += allPixelData[dataIndex];
sumG += allPixelData[dataIndex + 1];
sumB += allPixelData[dataIndex + 2];
// Alpha (allPixelData[dataIndex + 3]) is ignored for simplicity in this version
numPixelsInBlock++;
}
}
if (numPixelsInBlock === 0) {
currentLineEmojis += ' '; // Add a space for empty or problematic blocks
continue;
}
const avgR = sumR / numPixelsInBlock;
const avgG = sumG / numPixelsInBlock;
const avgB = sumB / numPixelsInBlock;
// Find the emoji in the palette that is closest to the average block color
let bestMatchEmoji = emojiPalette[0].emoji;
let minColorDistance = Infinity;
for (const paletteEntry of emojiPalette) {
const distance = colorDistanceSquared(avgR, avgG, avgB, paletteEntry.r, paletteEntry.g, paletteEntry.b);
if (distance < minColorDistance) {
minColorDistance = distance;
bestMatchEmoji = paletteEntry.emoji;
}
}
currentLineEmojis += bestMatchEmoji;
}
linesArray.push(currentLineEmojis);
}
outputPre.textContent = linesArray.join('\n');
return outputPre;
}
Apply Changes
I absolutely love this, thank you!