You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, sourceLang = 'auto', targetLang = 'en') {
/**
* Dynamically loads a script from a URL into the document head.
* @param {string} url The URL of the script to load.
* @returns {Promise<void>} A promise that resolves when the script is loaded.
*/
const loadScript = (url) => {
return new Promise((resolve, reject) => {
const existingScript = document.querySelector(`script[src="${url}"]`);
if (existingScript) {
// If the script tag exists, check if the global object it creates is available.
// For Tesseract.js, this is window.Tesseract.
if (window.Tesseract) {
resolve();
} else {
// The script might be loading. Listen to its load/error events.
existingScript.addEventListener('load', () => resolve());
existingScript.addEventListener('error', (e) => reject(new Error(`Script load error for ${url}`)));
}
return;
}
const script = document.createElement('script');
script.src = url;
script.onload = () => resolve();
script.onerror = () => reject(new Error(`Script load error for ${url}`));
document.head.appendChild(script);
});
};
/**
* Creates a styled DIV element to display error messages.
* @param {string} message The error message to display.
* @returns {HTMLDivElement} The created error element.
*/
const createErrorElement = (message) => {
const errorDiv = document.createElement('div');
errorDiv.style.color = 'red';
errorDiv.style.padding = '20px';
errorDiv.style.fontFamily = 'Arial, sans-serif';
errorDiv.style.border = '1px solid red';
errorDiv.style.backgroundColor = '#ffeeee';
errorDiv.innerText = message;
return errorDiv;
};
try {
await loadScript('https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js');
} catch (error) {
console.error("Failed to load Tesseract.js:", error);
return createErrorElement('Error: Could not load the OCR library. Please check your network connection.');
}
const canvas = document.createElement('canvas');
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
const ctx = canvas.getContext('2d', {
willReadFrequently: true
});
ctx.drawImage(originalImg, 0, 0);
let scheduler;
try {
// Tesseract.js uses 3-letter ISO 639-2 codes (e.g., 'eng', 'spa').
// The translation API uses 2-letter ISO 639-1 codes (e.g., 'en', 'es').
// We'll create a small map for common languages to pass the correct code to Tesseract.
const tesseractLangMap = {
'en': 'eng', 'es': 'spa', 'fr': 'fra', 'de': 'deu', 'it': 'ita',
'pt': 'por', 'ja': 'jpn', 'ko': 'kor', 'zh-CN': 'chi_sim',
'zh-TW': 'chi_tra', 'ru': 'rus', 'ar': 'ara', 'hi': 'hin'
};
const ocrLang = tesseractLangMap[sourceLang] || (sourceLang !== 'auto' ? sourceLang : 'eng');
scheduler = Tesseract.createScheduler();
const worker = await Tesseract.createWorker(ocrLang, 1, {
logger: m => console.log(m) // Logs progress to the console
});
scheduler.addWorker(worker);
const { data: { lines } } = await scheduler.recognize(originalImg);
if (!lines || lines.length === 0) {
console.log("No text was detected in the image.");
await scheduler.terminate();
return canvas; // Return the original image drawn on the canvas
}
for (const line of lines) {
const bbox = line.bbox;
const originalText = line.text.trim();
if (!originalText || originalText.length < 1) continue;
let translatedText = originalText;
try {
const apiUrl = `https://api.mymemory.translated.net/get?q=${encodeURIComponent(originalText)}&langpair=${sourceLang}|${targetLang}`;
const response = await fetch(apiUrl);
if (!response.ok) throw new Error(`API request failed with status ${response.status}`);
const translationData = await response.json();
if (translationData.responseData && translationData.responseData.translatedText) {
translatedText = translationData.responseData.translatedText;
} else if (translationData.matches && translationData.matches.length > 0) {
// Fallback to the first match if primary one fails
translatedText = translationData.matches[0].translation;
}
} catch (e) {
console.error(`Translation API call failed for "${originalText}":`, e);
// Keep original text if translation fails
}
// Cover up the original text by sampling a background color near the text box.
const sampleX = bbox.x0 > 2 ? bbox.x0 - 2 : bbox.x1 + 2;
const sampleY = bbox.y0 > 2 ? bbox.y0 - 2 : bbox.y1 + 2;
const clampedX = Math.max(0, Math.min(canvas.width - 1, sampleX));
const clampedY = Math.max(0, Math.min(canvas.height - 1, sampleY));
const pixelData = ctx.getImageData(clampedX, clampedY, 1, 1).data;
ctx.fillStyle = `rgb(${pixelData[0]}, ${pixelData[1]}, ${pixelData[2]})`;
// Slightly inflate the box to ensure full coverage of the original text
ctx.fillRect(bbox.x0 - 2, bbox.y0 - 2, (bbox.x1 - bbox.x0) + 4, (bbox.y1 - bbox.y0) + 4);
// Draw the translated text, adjusting font size to fit.
// Using a stroke behind the text improves readability on complex backgrounds.
ctx.fillStyle = 'black';
ctx.strokeStyle = 'white';
ctx.lineWidth = 4; // Adjust stroke width for better visibility
let fontSize = (bbox.y1 - bbox.y0) * 0.8; // Start with a font size proportional to the line height
do {
ctx.font = `bold ${fontSize}px 'Arial', sans-serif`;
if(ctx.measureText(translatedText).width < (bbox.x1 - bbox.x0)) {
break;
}
fontSize--;
} while (fontSize > 5);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const textX = bbox.x0 + (bbox.x1 - bbox.x0) / 2;
const textY = bbox.y0 + (bbox.y1 - bbox.y0) / 2;
ctx.strokeText(translatedText, textX, textY);
ctx.fillText(translatedText, textX, textY);
}
await scheduler.terminate();
} catch (error) {
console.error("An error occurred during the image processing:", error);
if (scheduler) await scheduler.terminate();
return createErrorElement(`Processing Error: ${error.message || 'An unknown error occurred.'}`);
}
return canvas;
}
Apply Changes