You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, targetLang = 'en', sourceLang = 'en') {
/**
* Dynamically loads the Tesseract.js script from a CDN if it's not already present.
* @returns {Promise<void>} A promise that resolves when the script is loaded.
*/
const ensureTesseract = () => {
return new Promise((resolve, reject) => {
// Check if Tesseract is already available in the global scope
if (window.Tesseract) {
return resolve();
}
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js';
script.onload = resolve;
script.onerror = () => reject(new Error('Failed to load Tesseract.js script. Please check your internet connection and ad-blockers.'));
document.head.appendChild(script);
});
};
/**
* Maps 2-letter ISO 639-1 language codes to 3-letter Tesseract language codes.
* @param {string} isoCode - The 2-letter language code (e.g., 'en').
* @returns {string} The corresponding Tesseract language code (e.g., 'eng').
*/
const getTesseractLangCode = (isoCode) => {
const map = {
en: 'eng', es: 'spa', fr: 'fra', de: 'deu', it: 'ita',
pt: 'por', ru: 'rus', ja: 'jpn', ko: 'kor', ar: 'ara',
'zh-CN': 'chi_sim', 'zh-TW': 'chi_tra'
};
// Handle common case where user might just provide 'zh'
if (isoCode === 'zh') return 'chi_sim';
return map[isoCode] || 'eng'; // Default to English if not found
};
// Create a canvas to work on, preserving the original image dimensions.
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, canvas.width, canvas.height);
try {
// Step 1: Ensure Tesseract.js is loaded
await ensureTesseract();
const tesseractLang = getTesseractLangCode(sourceLang);
// Logger for showing progress in the developer console
const logger = ({ status, progress }) => {
let message = `[OCR] ${status}`;
if (progress && status === 'recognizing text') {
message += `: ${(progress * 100).toFixed(1)}%`;
}
console.log(message);
};
// Step 2: Initialize Tesseract worker and perform Optical Character Recognition (OCR)
const worker = await Tesseract.createWorker(tesseractLang, 1, { logger });
const { data: { lines } } = await worker.recognize(canvas);
if (!lines || lines.length === 0) {
console.log("No text found in the image.");
await worker.terminate();
return canvas; // Return the original image on the canvas
}
// Step 3: Prepare recognized text for a batch translation request
const originalLinesText = lines.map(line => line.text.trim());
const separator = ' [|||] ';
const textToTranslate = originalLinesText.join(separator);
// Step 4: Translate text using a free, key-less API (MyMemory)
console.log(`[Translate] Requesting translation from '${sourceLang}' to '${targetLang}'...`);
const apiUrl = `https://api.mymemory.translated.net/get?q=${encodeURIComponent(textToTranslate)}&langpair=${sourceLang}|${targetLang}`;
const response = await fetch(apiUrl);
if (!response.ok) {
throw new Error(`Translation API failed with status: ${response.status}`);
}
const translationData = await response.json();
if (translationData.responseStatus !== 200) {
throw new Error(`Translation API error: ${translationData.responseDetails}`);
}
// Split the translated blob back into individual lines
const translatedFullText = translationData.responseData.translatedText;
const translatedLines = translatedFullText.split(separator.trim()).map(line => line.trim());
if (originalLinesText.length !== translatedLines.length) {
console.warn("Translation line count mismatch. Layout may be affected.");
}
// Step 5: Draw the translated text over the original text on the canvas
console.log("[Render] Reconstructing image with translated text...");
lines.forEach((line, index) => {
const translatedText = translatedLines[index];
if (!translatedText || !line.text.trim()) return;
const bbox = line.bbox;
// 5a. Cover the old text by sampling a background color from just outside the text box.
const sampleX = Math.max(0, bbox.x0 - 5);
const sampleY = Math.max(0, bbox.y0 + Math.floor(bbox.height / 2));
const pixelData = ctx.getImageData(sampleX, sampleY, 1, 1).data;
const bgColor = `rgb(${pixelData[0]}, ${pixelData[1]}, ${pixelData[2]})`;
ctx.fillStyle = bgColor;
ctx.fillRect(bbox.x0, bbox.y0, bbox.width, bbox.height);
// 5b. Determine a contrasting text color (black or white) for readability.
const brightness = (pixelData[0] * 299 + pixelData[1] * 587 + pixelData[2] * 114) / 1000;
ctx.fillStyle = brightness > 128 ? 'black' : 'white';
ctx.textBaseline = "middle";
ctx.textAlign = "left";
// 5c. Automatically adjust font size so the translated text fits in the original's bounding box.
let fontSize = bbox.height;
do {
ctx.font = `bold ${fontSize}px "Arial", sans-serif`;
fontSize -= 1;
} while (fontSize > 5 && ctx.measureText(translatedText).width > bbox.width);
// 5d. Draw the new text, vertically centered in the bounding box.
const yPos = bbox.y0 + bbox.height / 2;
ctx.fillText(translatedText, bbox.x0, yPos);
});
// Step 6: Clean up the Tesseract worker to free up resources
await worker.terminate();
console.log("Processing complete.");
return canvas;
} catch (error) {
console.error("Image translation failed:", error);
// Draw the error message on the canvas for user feedback
ctx.fillStyle = 'rgba(255, 0, 0, 0.8)';
ctx.fillRect(0, canvas.height - 40, canvas.width, 40);
ctx.fillStyle = 'white';
ctx.font = 'bold 16px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(`Error: ${error.message}`, canvas.width / 2, canvas.height - 20);
return canvas;
}
}
Apply Changes