You can edit the below JavaScript code to customize the image tool.
Apply Changes
/**
* Simulates an "AI Translator" or "Alphabet Creator" for an image.
*
* In 'translate' mode, it overlays pseudo-translated text onto the image.
* In 'alphabet' mode, it generates a new visual "alphabet" from patches of the image
* and displays a legend at the bottom.
*
* @param {Image} originalImg The original Javascript Image object.
* @param {string} mode The operation to perform: 'translate' or 'alphabet'. Defaults to 'translate'.
* @param {string} text The text to "translate" or use for the alphabet creation. Defaults to 'HELLO WORLD'.
* @param {string} style In 'translate' mode, this defines the translation style. Can be 'pseudo-cyrillic', 'pseudo-runic', or 'lorem-ipsum'. Defaults to 'pseudo-cyrillic'. This parameter is ignored in 'alphabet' mode.
* @param {number} seed A number used to seed the random patch selection in 'alphabet' mode for consistent results. Defaults to 12345. This parameter is ignored in 'translate' mode.
* @returns {HTMLCanvasElement} A new canvas element with the processed image.
*/
async function processImage(originalImg, mode = 'translate', text = 'HELLO WORLD', style = 'pseudo-cyrillic', seed = 12345) {
// 1. Canvas Setup
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Ensure image is loaded before getting dimensions
await originalImg.decode();
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
// Draw the original image as the background
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
// --- Helper Functions ---
// Simple seeded pseudo-random number generator for predictable "randomness"
const seededRandom = (s) => {
return function() {
s = Math.sin(s) * 10000;
return s - Math.floor(s);
};
};
// Analyzes the image to find a contrasting color (black or white) for text
const getContrastingColor = () => {
const sampleSize = 100; // Performance: analyze a smaller thumbnail
const tempCanvas = document.createElement('canvas');
tempCanvas.width = sampleSize;
tempCanvas.height = sampleSize;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.drawImage(originalImg, 0, 0, sampleSize, sampleSize);
const imageData = tempCtx.getImageData(0, 0, sampleSize, sampleSize);
const data = imageData.data;
let r = 0, g = 0, b = 0;
for (let i = 0; i < data.length; i += 4) {
r += data[i];
g += data[i + 1];
b += data[i + 2];
}
const pixelCount = data.length / 4;
const avgLuminance = (0.299 * (r / pixelCount) + 0.587 * (g / pixelCount) + 0.114 * (b / pixelCount));
// Return 'black' for light images, 'white' for dark images
return avgLuminance > 128 ? 'black' : 'white';
};
// --- Main Logic ---
if (mode === 'translate') {
let translatedText = '';
const upperText = text.toUpperCase();
const pseudoCyrillicMap = { 'A':'Д','B':'Б','C':'Ц','D':'Д','E':'Є','F':'F','G':'G','H':'Н','I':'І','J':'J','K':'К','L':'Л','M':'М','N':'И','O':'О','P':'Р','Q':'Q','R':'Я','S':'Ѕ','T':'Т','U':'Ц','V':'V','W':'Ш','X':'Х','Y':'У','Z':'Z',' ':' ' };
const pseudoRunicMap = { 'A':'ᚨ','B':'ᛒ','C':'ᚲ','D':'ᛞ','E':'ᛖ','F':'ᚠ','G':'ᚷ','H':'ᚺ','I':'ᛁ','J':'ᛃ','K':'ᚲ','L':'ᛚ','M':'ᛗ','N':'ᚾ','O':'ᛟ','P':'ᛈ','Q':'ᛩ','R':'ᚱ','S':'ᛊ','T':'ᛏ','U':'ᚢ','V':'ᚹ','W':'ᚹ','X':'ᛪ','Y':'ᛦ','Z':'ᛉ',' ':' ' };
const loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin nibh augue, suscipit a, scelerisque sed, lacinia in, mi. Cras vel lorem.";
switch (style) {
case 'pseudo-runic':
translatedText = Array.from(upperText).map(char => pseudoRunicMap[char] || char).join('');
break;
case 'lorem-ipsum':
translatedText = loremIpsum.substring(0, text.length);
break;
case 'pseudo-cyrillic':
default:
translatedText = Array.from(upperText).map(char => pseudoCyrillicMap[char] || char).join('');
break;
}
const fontSize = Math.max(24, Math.floor(canvas.width / 20));
ctx.font = `bold ${fontSize}px 'Trebuchet MS', Arial, sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const textColor = getContrastingColor();
ctx.fillStyle = textColor;
// Add a stroke/shadow for better visibility on complex backgrounds
ctx.shadowColor = textColor === 'white' ? 'black' : 'white';
ctx.shadowBlur = 8;
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.fillText(translatedText, canvas.width / 2, canvas.height / 2);
} else if (mode === 'alphabet') {
const legendHeight = Math.min(canvas.height * 0.4, 250);
ctx.fillStyle = 'rgba(0, 0, 0, 0.75)';
ctx.fillRect(0, canvas.height - legendHeight, canvas.width, legendHeight);
const uniqueChars = [...new Set(text.toUpperCase().replace(/\s/g, ''))];
const patchSize = Math.max(20, Math.floor(legendHeight / 5));
const fontSize = Math.floor(patchSize * 0.8);
const padding = 15;
let x = padding;
let y = canvas.height - legendHeight + padding * 2 + fontSize;
ctx.font = `bold ${fontSize}px 'Courier New', monospace`;
ctx.fillStyle = 'white';
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.shadowColor = 'transparent'; // Reset shadow for legend text
ctx.fillText("Generated Alphabet Key:", padding, canvas.height - legendHeight + padding);
const random = seededRandom(seed);
const alphabetMap = new Map();
// Generate random, non-overlapping locations for each unique char
for (const char of uniqueChars) {
alphabetMap.set(char, {
x: Math.floor(random() * (canvas.width - patchSize)),
y: Math.floor(random() * (canvas.height - patchSize - legendHeight))
});
}
for (const char of uniqueChars) {
const pos = alphabetMap.get(char);
const textWidth = ctx.measureText(`${char} = `).width;
if (x + textWidth + patchSize + padding > canvas.width) {
x = padding;
y += patchSize + padding;
}
if (y + patchSize > canvas.height) break; // Stop if we run out of space
ctx.fillText(`${char} =`, x, y + (patchSize - fontSize) / 2);
ctx.drawImage(originalImg, pos.x, pos.y, patchSize, patchSize, x + textWidth + 5, y, patchSize, patchSize);
ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
ctx.lineWidth = 1;
ctx.strokeRect(x + textWidth + 5, y, patchSize, patchSize);
x += textWidth + patchSize + padding;
}
}
return canvas;
}
Apply Changes