You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, vegSize = "15", blendOriginal = "0.35") {
// Parse parameters
const size = Math.max(5, parseInt(vegSize, 10)) || 15;
const opacity = Math.max(0, Math.min(1, parseFloat(blendOriginal))) || 0;
const width = originalImg.width;
const height = originalImg.height;
// Main canvas for drawing
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// Offscreen canvas for reading original image pixels
const readCanvas = document.createElement('canvas');
readCanvas.width = width;
readCanvas.height = height;
const rCtx = readCanvas.getContext('2d', { willReadFrequently: true });
rCtx.drawImage(originalImg, 0, 0);
const imgData = rCtx.getImageData(0, 0, width, height);
const data = imgData.data;
// List of "vegetable giant" emojis
const emojis = [
'🍅', '🍆', '🥑', '🥦', '🥬', '🥒', '🌶️', '🫑', '🌽', '🥕',
'🧄', '🧅', '🥔', '🍠', '🍄', '🎃', '🫛', '🥗', '🌻'
];
// Pre-calculate the average color of each emoji
const emojiDict = [];
const tCanvas = document.createElement('canvas');
tCanvas.width = size * 2;
tCanvas.height = size * 2;
const tCtx = tCanvas.getContext('2d', { willReadFrequently: true });
tCtx.font = `${size}px sans-serif`;
tCtx.textAlign = 'center';
tCtx.textBaseline = 'middle';
for (let e of emojis) {
tCtx.clearRect(0, 0, tCanvas.width, tCanvas.height);
tCtx.fillText(e, size, size);
let eData = tCtx.getImageData(0, 0, tCanvas.width, tCanvas.height).data;
let r = 0, g = 0, b = 0, a = 0;
for (let i = 0; i < eData.length; i += 4) {
if (eData[i + 3] > 10) { // Discard highly transparent pixels
r += eData[i];
g += eData[i + 1];
b += eData[i + 2];
a++;
}
}
if (a > 0) {
emojiDict.push({ char: e, r: r / a, g: g / a, b: b / a });
} else {
// Fallback for non-rendering emojis
emojiDict.push({ char: e, r: 128, g: 128, b: 128 });
}
}
// Prepare main canvas for rendering "Vegetables"
ctx.clearRect(0, 0, width, height);
// Draw font slightly larger than grid size to overlap and eliminate gaps
ctx.font = `${Math.floor(size * 1.35)}px sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// Process original image grid by grid
for (let y = 0; y < height; y += size) {
for (let x = 0; x < width; x += size) {
let r = 0, g = 0, b = 0, count = 0;
// Sample the current grid cell
for (let by = 0; by < size; by++) {
for (let bx = 0; bx < size; bx++) {
let px = x + bx;
let py = y + by;
if (px < width && py < height) {
let idx = (py * width + px) * 4;
if (data[idx + 3] > 15) { // Ensure pixel is visibly opaque
r += data[idx];
g += data[idx + 1];
b += data[idx + 2];
count++;
}
}
}
}
// If the block is largely opaque, draw a matching vegetable component
if (count > (size * size) * 0.15) {
r /= count;
g /= count;
b /= count;
// Find the closest vegetable matching the cell's average color
// using a human-perception weighted RGB distance formula
let bestE = emojiDict[0];
let bestDist = Infinity;
for (let e of emojiDict) {
let dr = e.r - r;
let dg = e.g - g;
let db = e.b - b;
let dist = (dr * dr * 0.3) + (dg * dg * 0.59) + (db * db * 0.11);
if (dist < bestDist) {
bestDist = dist;
bestE = e;
}
}
// Draw the selected vegetable component randomly rotated slightly for organic feel
const cx = x + size / 2;
const cy = y + size / 2;
const offsetX = (Math.random() - 0.5) * (size * 0.2);
const offsetY = (Math.random() - 0.5) * (size * 0.2);
ctx.save();
ctx.translate(cx + offsetX, cy + offsetY);
ctx.rotate((Math.random() - 0.5) * 0.5); // Random rotation
ctx.fillText(bestE.char, 0, 0);
ctx.restore();
}
}
}
// Blend the original image overlay minimally to help define faces/shapes
if (opacity > 0) {
ctx.globalAlpha = opacity;
ctx.globalCompositeOperation = 'overlay';
ctx.drawImage(originalImg, 0, 0);
ctx.globalCompositeOperation = 'source-over';
ctx.globalAlpha = opacity * 0.5; // Final fine softening
ctx.drawImage(originalImg, 0, 0);
}
return canvas;
}
Apply Changes