You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, sourceLang = "eng", targetLanguages = "ru,es,fr,zh-CN", textColor = "auto", bgColor = "auto") {
// Create the container element that will be returned immediately
const container = document.createElement('div');
container.style.width = '100%';
container.style.fontFamily = 'system-ui, -apple-system, sans-serif';
container.style.display = 'flex';
container.style.flexDirection = 'column';
container.style.alignItems = 'center';
const statusDiv = document.createElement('div');
statusDiv.style.width = '100%';
statusDiv.style.padding = '15px';
statusDiv.style.backgroundColor = '#f8f9fa';
statusDiv.style.border = '1px solid #dee2e6';
statusDiv.style.borderRadius = '5px';
statusDiv.style.marginBottom = '15px';
statusDiv.style.color = '#495057';
statusDiv.style.boxSizing = 'border-box';
statusDiv.style.textAlign = 'center';
statusDiv.innerText = 'Initializing OCR and translation engine... please wait.';
container.appendChild(statusDiv);
// Run async workflow so we don't block returning the container DOM element
(async () => {
try {
const langs = targetLanguages.split(',').map(s => s.trim()).filter(Boolean);
if (langs.length === 0) langs.push("ru"); // Default to Russian if empty
// Scale image if it's too large to prevent out-of-memory errors and speed up processing
let imgW = originalImg.width || originalImg.naturalWidth;
let imgH = originalImg.height || originalImg.naturalHeight;
const maxDimension = 1200;
if (imgW > maxDimension || imgH > maxDimension) {
const scale = Math.min(maxDimension / imgW, maxDimension / imgH);
imgW = Math.round(imgW * scale);
imgH = Math.round(imgH * scale);
}
const ocrCanvas = document.createElement('canvas');
ocrCanvas.width = imgW;
ocrCanvas.height = imgH;
const ocrCtx = ocrCanvas.getContext('2d');
ocrCtx.drawImage(originalImg, 0, 0, imgW, imgH);
// 1. Dynamically Load Tesseract
statusDiv.innerText = 'Loading text recognition models...';
if (typeof Tesseract === 'undefined') {
const script = document.createElement('script');
script.src = 'https://unpkg.com/tesseract.js@4.1.1/dist/tesseract.min.js';
document.head.appendChild(script);
await new Promise((resolve, reject) => {
script.onload = resolve;
script.onerror = reject;
});
}
// 2. OCR Original Image
statusDiv.innerText = 'Extracting text from image (this may take a few moments)...';
const worker = await Tesseract.createWorker(sourceLang);
const ret = await worker.recognize(ocrCanvas);
await worker.terminate();
const lines = ret.data.lines;
if (!lines || lines.length === 0) {
statusDiv.innerText = 'No readable text was detected in the image.';
container.appendChild(ocrCanvas);
return;
}
const imgData = ocrCtx.getImageData(0, 0, ocrCanvas.width, ocrCanvas.height);
// 3. Create Final Stacked Canvas
const headerHeight = 60;
const outCanvas = document.createElement('canvas');
outCanvas.width = ocrCanvas.width;
outCanvas.height = (ocrCanvas.height + headerHeight) * langs.length;
const outCtx = outCanvas.getContext('2d');
outCanvas.style.maxWidth = '100%';
outCanvas.style.height = 'auto';
outCanvas.style.border = '1px solid #ccc';
outCanvas.style.boxShadow = '0 4px 6px rgba(0,0,0,0.1)';
const langNames = {
"ru": "Русский (Russian)", "es": "Español (Spanish)", "fr": "Français (French)",
"de": "Deutsch (German)", "zh-CN": "中文 (Chinese)", "ar": "العربية (Arabic)",
"hi": "हिन्दी (Hindi)", "ja": "日本語 (Japanese)", "pt": "Português (Portuguese)",
"it": "Italiano (Italian)", "ko": "한국어 (Korean)", "en": "English"
};
let pY = 0;
// Render translated version per requested language
for (let i = 0; i < langs.length; i++) {
const lang = langs[i];
statusDiv.innerText = `Translating to ${langNames[lang] || lang} (${i + 1}/${langs.length})...`;
// Draw Target Language Banner
outCtx.fillStyle = '#2c3e50';
outCtx.fillRect(0, pY, outCanvas.width, headerHeight);
outCtx.fillStyle = '#ecf0f1';
outCtx.textBaseline = 'middle';
outCtx.textAlign = 'center';
let headerFontSize = 24;
const headerText = `World Language: ${langNames[lang] || lang.toUpperCase()}`;
outCtx.font = `bold ${headerFontSize}px sans-serif`;
while(headerFontSize > 12 && outCtx.measureText(headerText).width > outCanvas.width - 20) {
headerFontSize--;
outCtx.font = `bold ${headerFontSize}px sans-serif`;
}
outCtx.fillText(headerText, outCanvas.width / 2, pY + headerHeight / 2);
pY += headerHeight;
// Draw Initial Frame Image
outCtx.drawImage(ocrCanvas, 0, pY, imgW, imgH);
// Translate & Overlay text onto the frame
for (const line of lines) {
const text = line.text.trim();
if (!text) continue;
const bbox = line.bbox;
let translated = text;
// Fetch Free Google Translate
try {
const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=${lang}&dt=t&q=${encodeURIComponent(text)}`;
const res = await fetch(url);
if (res.ok) {
const data = await res.json();
translated = data[0].map(item => item[0]).join("");
}
} catch (err) {
console.error("Translation blocked or failed:", err);
}
// Erase original text bounds with boundary average color or user selection
let boxBg = bgColor;
if (bgColor === 'auto') {
const margin = 2;
let r = 0, g = 0, b = 0, count = 0;
const d = imgData.data;
const addSample = (px, py) => {
px = Math.floor(px); py = Math.floor(py);
if (px >= 0 && px < imgW && py >= 0 && py < imgH) {
const i = (py * imgW + px) * 4;
r += d[i]; g += d[i+1]; b += d[i+2]; count++;
}
};
for(let x = bbox.x0; x <= bbox.x1; x += 4) {
addSample(x, bbox.y0 - margin);
addSample(x, bbox.y1 + margin);
}
for(let y = bbox.y0; y <= bbox.y1; y += 4) {
addSample(bbox.x0 - margin, y);
addSample(bbox.x1 + margin, y);
}
if (count > 0) boxBg = `rgb(${Math.round(r/count)}, ${Math.round(g/count)}, ${Math.round(b/count)})`;
else boxBg = '#ffffff';
}
outCtx.fillStyle = boxBg;
outCtx.fillRect(bbox.x0, pY + bbox.y0, bbox.x1 - bbox.x0, bbox.y1 - bbox.y0);
// Compute text styling based on boundary contrast
const boxWidth = bbox.x1 - bbox.x0;
const boxHeight = bbox.y1 - bbox.y0;
let contrastColor = '#000000';
const match = boxBg.match(/\d+/g);
if (match && match.length >= 3) {
const yiq = ((parseInt(match[0])*299)+(parseInt(match[1])*587)+(parseInt(match[2])*114))/1000;
contrastColor = (yiq >= 128) ? '#000000' : '#ffffff';
}
// Render readable translated text inside erased boundary box
outCtx.fillStyle = textColor === 'auto' ? contrastColor : textColor;
outCtx.textBaseline = 'middle';
outCtx.textAlign = 'center';
let fontSize = boxHeight * 0.75;
outCtx.font = `bold ${fontSize}px sans-serif`;
let textWidth = outCtx.measureText(translated).width;
while (textWidth > boxWidth * 0.95 && fontSize > 8) {
fontSize -= 1;
outCtx.font = `bold ${fontSize}px sans-serif`;
textWidth = outCtx.measureText(translated).width;
}
const x = bbox.x0 + boxWidth / 2;
const y = pY + bbox.y0 + boxHeight / 2 + (fontSize * 0.05);
outCtx.fillText(translated, x, y);
}
pY += imgH;
}
statusDiv.innerText = 'Translation dynamically completed!';
statusDiv.style.backgroundColor = '#d4edda';
statusDiv.style.color = '#155724';
statusDiv.style.borderColor = '#c3e6cb';
container.appendChild(outCanvas);
setTimeout(() => { statusDiv.style.display = 'none'; }, 3500);
} catch (err) {
console.error(err);
statusDiv.innerText = 'An error occurred processing the image: ' + err.message;
statusDiv.style.backgroundColor = '#f8d7da';
statusDiv.style.color = '#721c24';
statusDiv.style.borderColor = '#f5c6cb';
}
})();
return container;
}
Apply Changes