You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, targetLang = 'en', sourceLang = 'eng') {
/**
* Dynamically loads a script and returns a promise that resolves when the script is loaded.
* @param {string} url - The URL of the script to load.
* @returns {Promise<void>}
*/
const loadScript = (url) => {
return new Promise((resolve, reject) => {
// Check if Tesseract is already available
if (window.Tesseract) {
return resolve();
}
// Check if script tag already exists
let script = document.querySelector(`script[src="${url}"]`);
if (script) {
// If it exists, add load listeners and resolve when done
script.addEventListener('load', () => resolve(), {
once: true
});
script.addEventListener('error', () => reject(new Error(`Script load error for ${url}`)), {
once: true
});
} else {
// Otherwise, create and append the script
script = document.createElement('script');
script.src = url;
script.onload = resolve;
script.onerror = () => reject(new Error(`Failed to load script: ${url}`));
document.head.appendChild(script);
}
});
};
/**
* Translates a given text using a free public API.
* @param {string} text - The text to translate.
* @param {string} from - The source language code (2 letters, e.g., 'en').
* @param {string} to - The target language code (2 letters, e.g., 'es').
* @returns {Promise<string>} - The translated text, or original text on failure.
*/
const translateText = async (text, from, to) => {
if (!text || text.trim() === '') {
return '';
}
try {
const apiUrl = `https://api.mymemory.translated.net/get?q=${encodeURIComponent(text)}&langpair=${from}|${to}`;
const response = await fetch(apiUrl);
if (!response.ok) {
console.error('Translation API request failed:', response.statusText);
return text; // Return original text on API-level failure
}
const data = await response.json();
if (data.responseData && data.responseData.translatedText) {
// Decode HTML entities that the API sometimes returns
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.responseData.translatedText;
return tempDiv.textContent || tempDiv.innerText || "";
} else {
return text; // Return original text if translation not found
}
} catch (error) {
console.error('Error during translation:', error);
return text; // Return original text on network error
}
};
/**
* Calculates the largest font size that allows a single line of text to fit within given dimensions.
* @param {CanvasRenderingContext2D} ctx - The canvas rendering context.
* @param {string} text - The text to measure.
* @param {number} maxWidth - The maximum width for the text.
* @param {number} maxHeight - The maximum height for the text.
* @returns {string} - The CSS font property string (e.g., "20px sans-serif").
*/
const getFittingFont = (ctx, text, maxWidth, maxHeight) => {
let fontSize = Math.floor(maxHeight);
if (fontSize <= 0) return `1px sans-serif`;
while (fontSize > 0) {
ctx.font = `${fontSize}px sans-serif`;
const metrics = ctx.measureText(text);
if (metrics.width < maxWidth) {
return `${fontSize}px sans-serif`;
}
fontSize -= 1;
}
return `1px sans-serif`; // Fallback for text that cannot fit
};
// --- Main Logic ---
const TESSERACT_CDN = 'https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js';
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
let worker; // Tesseract worker instance
try {
// Step 1: Initialize canvas and show a processing message
ctx.drawImage(originalImg, 0, 0);
ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = `bold ${Math.min(canvas.width, canvas.height) / 15}px sans-serif`;
ctx.fillText('Initializing...', canvas.width / 2, canvas.height / 2);
// Step 2: Load Tesseract.js library
await loadScript(TESSERACT_CDN);
// Step 3: Perform OCR
ctx.fillText('Performing OCR...', canvas.width / 2, canvas.height / 2);
// Tesseract language codes are 3 letters (e.g., 'eng', 'rus', 'jpn')
worker = await Tesseract.createWorker(sourceLang);
const {
data: {
paragraphs
}
} = await worker.recognize(canvas);
// Step 4: Translate the recognized text
ctx.fillText('Translating text...', canvas.width / 2, canvas.height / 2);
// Note: Translation API uses 2-letter codes. This conversion is a heuristic.
const sourceLang2Letter = sourceLang.substring(0, 2);
const translationPromises = paragraphs.map(p => translateText(p.text, sourceLang2Letter, targetLang));
const translatedTexts = await Promise.all(translationPromises);
// Step 5: Render the final image
// Redraw the original image to clear the processing messages
ctx.drawImage(originalImg, 0, 0);
// Overlay the translated text
paragraphs.forEach((p, index) => {
const translatedText = translatedTexts[index];
if (!translatedText || translatedText.trim() === '') return;
const bbox = p.bbox;
const textToDraw = translatedText.replace(/(\r\n|\n|\r)/gm, " "); // Flatten to single line
// Cover the original text with a white background
ctx.fillStyle = 'white';
ctx.fillRect(bbox.x0, bbox.y0, bbox.x1 - bbox.x0, bbox.y1 - bbox.y0);
// Draw the translated text, fitting it into the box
ctx.fillStyle = 'black';
ctx.textBaseline = 'top';
ctx.textAlign = 'left';
const font = getFittingFont(ctx, textToDraw, bbox.x1 - bbox.x0, bbox.y1 - bbox.y0);
ctx.font = font;
// Vertically center the text within the bounding box
const textMetrics = ctx.measureText(textToDraw);
const fontHeight = textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent;
const yPos = bbox.y0 + ((bbox.y1 - bbox.y0) - fontHeight) / 2;
ctx.fillText(textToDraw, bbox.x0, yPos);
});
return canvas;
} catch (error) {
console.error("An error occurred during image translation:", error);
// In case of an error, return the original image with an error message overlay
ctx.drawImage(originalImg, 0, 0);
ctx.fillStyle = 'rgba(200, 0, 0, 0.8)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const fontSize = Math.min(canvas.width, canvas.height) / 20;
ctx.font = `bold ${fontSize}px sans-serif`;
ctx.fillText('An Error Occurred', canvas.width / 2, canvas.height / 2 - fontSize);
ctx.font = `${fontSize * 0.7}px sans-serif`;
ctx.fillText(error.message || 'Please check console for details.', canvas.width / 2, canvas.height / 2 + fontSize * 0.7);
return canvas;
} finally {
// Ensure the Tesseract worker is always terminated
if (worker) {
await worker.terminate();
}
}
}
Apply Changes