You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, sourceLang = 'eng', targetLang = 'spa') {
/**
* Dynamically loads a script and returns a promise that resolves when the script is loaded.
* @param {string} src - The URL of the script to load.
* @returns {Promise<void>}
*/
const loadScript = (src) => {
return new Promise((resolve, reject) => {
if (document.querySelector(`script[src="${src}"]`)) {
resolve();
return;
}
const script = document.createElement('script');
script.src = src;
script.onload = () => resolve();
script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
document.head.appendChild(script);
});
};
// --- 1. SETUP CANVAS ---
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
ctx.drawImage(originalImg, 0, 0);
/**
* Helper function to display progress messages on the canvas.
* @param {string} message - The text to display.
*/
const updateProgress = (message) => {
// Redraw image to clear previous text
ctx.drawImage(originalImg, 0, 0);
// Draw a semi-transparent overlay
ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw the progress text
ctx.fillStyle = 'white';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const fontSize = Math.max(24, Math.min(canvas.width / 20, canvas.height / 20));
ctx.font = `${fontSize}px Arial`;
ctx.fillText(message, canvas.width / 2, canvas.height / 2);
};
try {
// --- 2. LOAD TESSERACT.JS (OCR library) ---
updateProgress('Loading AI model...');
await loadScript('https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js');
// --- 3. RECOGNIZE TEXT (OCR) ---
const worker = await Tesseract.createWorker(sourceLang, 1, {
logger: m => {
if (m.status === 'recognizing text') {
const progress = (m.progress * 100).toFixed(0);
updateProgress(`Step 1/3: Recognizing text (${progress}%)`);
}
}
});
const { data: { lines } } = await worker.recognize(canvas);
await worker.terminate();
if (lines.length === 0) {
updateProgress('No text found in the image.');
return canvas;
}
// --- 4. TRANSLATE TEXT ---
updateProgress('Step 2/3: Translating text...');
// Tesseract uses 3-letter ISO 639-2 codes, but many free APIs use 2-letter ISO 639-1.
// This map helps convert them for the translation API call.
const langMap = {
'eng': 'en', 'spa': 'es', 'fra': 'fr', 'deu': 'de', 'jpn': 'ja',
'chi_sim': 'zh-CN', 'rus': 'ru', 'por': 'pt', 'ita': 'it', 'kor': 'ko'
};
const apiSourceLang = langMap[sourceLang] || sourceLang.substring(0, 2);
const apiTargetLang = langMap[targetLang] || targetLang.substring(0, 2);
const translationPromises = lines.map(line => {
const textToTranslate = line.text.trim();
if (!textToTranslate) return Promise.resolve('');
// Using the free MyMemory Translation API. It has limitations but requires no API key.
const url = `https://api.mymemory.translated.net/get?q=${encodeURIComponent(textToTranslate)}&langpair=${apiSourceLang}|${apiTargetLang}`;
return fetch(url)
.then(response => response.json())
.then(data => data.responseData.translatedText || textToTranslate)
.catch(() => textToTranslate); // On error, fallback to the original text.
});
const translatedLines = await Promise.all(translationPromises);
// --- 5. RENDER TRANSLATED IMAGE ---
updateProgress('Step 3/3: Rendering result...');
await new Promise(resolve => setTimeout(resolve, 300)); // Small delay for user to see the message
// Redraw a final time to clear the progress message.
ctx.drawImage(originalImg, 0, 0);
lines.forEach((line, index) => {
const translatedText = translatedLines[index];
if (!translatedText) return;
const bbox = line.bbox;
// A. Obscure the original text. A simple white box is effective.
// For a more advanced version, one might sample the surrounding colour.
ctx.fillStyle = 'white';
ctx.fillRect(bbox.x0, bbox.y0, bbox.x1 - bbox.x0, bbox.y1 - bbox.y0);
// B. Draw the new, translated text.
ctx.fillStyle = 'black';
ctx.textBaseline = 'top';
ctx.textAlign = 'left';
// Find the best font size to fit the text within the original bounding box.
let fontSize = (bbox.y1 - bbox.y0) * 0.9; // Start with 90% of the line height.
ctx.font = `${fontSize}px Arial`;
while (ctx.measureText(translatedText).width > (bbox.x1 - bbox.x0) && fontSize > 5) {
fontSize--;
ctx.font = `${fontSize}px Arial`;
}
// Vertically center the text within the line's bounding box.
const textMetrics = ctx.measureText(translatedText);
const textHeight = textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent;
const y = bbox.y0 + ((bbox.y1 - bbox.y0) - textHeight) / 2;
ctx.fillText(translatedText, bbox.x0, y);
});
} catch (error) {
console.error("Image processing failed:", error);
updateProgress(`An error occurred: ${error.message}`);
}
return canvas;
}
Apply Changes