You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, searchTopic = "") {
// 1. Ensure Tesseract.js is loaded for OCR
if (typeof window.Tesseract === 'undefined') {
await new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = 'https://unpkg.com/tesseract.js@v4.1.1/dist/tesseract.min.js';
script.onload = () => resolve();
script.onerror = () => reject(new Error("Failed to load Tesseract.js"));
document.head.appendChild(script);
});
}
// 2. Perform Optical Character Recognition (OCR)
// Tesseract.recognize automatically creates and terminates the web worker
const result = await window.Tesseract.recognize(originalImg, 'eng');
const { data } = result;
const words = data.words || [];
const lines = data.lines || [];
// 3. Process search filters
const topicStr = String(searchTopic).toLowerCase().trim();
const keywords = (topicStr && topicStr !== "all")
? topicStr.split(/\s+/).filter(w => w.length > 0)
: [];
let matchedWords = [];
if (keywords.length === 0) {
matchedWords = words; // Match everything if no specific query
} else {
matchedWords = words.filter(word => {
const text = word.text.toLowerCase();
return keywords.some(k => text.includes(k));
});
}
// 4. Create the main wrapper container
const container = document.createElement('div');
container.style.display = 'flex';
container.style.flexDirection = 'column';
container.style.gap = '16px';
container.style.fontFamily = 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif';
container.style.boxSizing = 'border-box';
container.style.width = '100%';
// 5. Setup Canvas for highlighting
const canvas = document.createElement('canvas');
canvas.width = originalImg.width;
canvas.height = originalImg.height;
canvas.style.maxWidth = '100%';
canvas.style.height = 'auto';
canvas.style.border = '1px solid #ccc';
canvas.style.borderRadius = '6px';
canvas.style.boxShadow = '0 4px 6px rgba(0,0,0,0.1)';
const ctx = canvas.getContext('2d');
// Draw the clean underlying image
ctx.drawImage(originalImg, 0, 0);
if (matchedWords.length > 0) {
// Apply a dark dim overlay to the entire image to make highlights pop
ctx.fillStyle = 'rgba(0, 0, 0, 0.65)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const strokeWidth = Math.max(2, Math.floor(canvas.width / 400)); // scale stroke for big images
ctx.strokeStyle = '#00FF00'; // Lime green
ctx.lineWidth = strokeWidth;
// Cutout the dim layer & stroke rectangles around matches
for (const word of matchedWords) {
const { x0, y0, x1, y1 } = word.bbox;
const width = x1 - x0;
const height = y1 - y0;
// Restoring original pixels via a clip path mask
ctx.save();
ctx.beginPath();
ctx.rect(x0, y0, width, height);
ctx.clip();
ctx.drawImage(originalImg, 0, 0);
ctx.restore();
// Draw bounding box
ctx.strokeRect(x0, y0, width, height);
}
} else {
// Overlay communicating no matches found
ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const fontSize = Math.max(16, Math.floor(canvas.width / 25));
ctx.font = `bold ${fontSize}px sans-serif`;
const message = words.length === 0
? 'No readable text detected in image.'
: `No text correctly matches: "${searchTopic}"`;
ctx.fillText(message, canvas.width / 2, canvas.height / 2);
}
container.appendChild(canvas);
// 6. Extracted Texts Section List Viewer below the image
const resultsDiv = document.createElement('div');
resultsDiv.style.backgroundColor = '#f8fafc';
resultsDiv.style.padding = '16px';
resultsDiv.style.border = '1px solid #e2e8f0';
resultsDiv.style.borderRadius = '6px';
const header = document.createElement('h3');
header.textContent = `Found Statements (${matchedWords.length > 0 ? (keywords.length > 0 ? "Filtered" : "All") : 0})`;
header.style.margin = '0 0 12px 0';
header.style.color = '#334155';
resultsDiv.appendChild(header);
const escapeHtml = (str) => {
return str.replace(/[&<>'"]/g,
tag => ({ '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' }[tag])
);
};
if (matchedWords.length > 0) {
const list = document.createElement('ul');
list.style.margin = '0';
list.style.paddingLeft = '24px';
list.style.color = '#475569';
list.style.maxHeight = '250px';
list.style.overflowY = 'auto'; // scrollable if many results
list.style.lineHeight = '1.5';
// Extract and filter readable lines
const matchedLines = [];
for (const line of lines) {
const lineStr = line.text.trim();
if (!lineStr) continue;
if (keywords.length === 0) {
matchedLines.push(lineStr);
} else {
const lowerLine = lineStr.toLowerCase();
if (keywords.some(k => lowerLine.includes(k))) {
matchedLines.push(lineStr);
}
}
}
for (const lineStr of matchedLines) {
const li = document.createElement('li');
li.style.marginBottom = '8px';
let htmlContent = escapeHtml(lineStr);
if (keywords.length > 0) {
keywords.forEach(keyword => {
const safeKey = escapeHtml(keyword).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`(${safeKey})`, 'gi');
htmlContent = htmlContent.replace(
regex,
'<strong style="color:#16a34a; background-color:#dcfce7; padding:0 4px; border-radius:3px;">$1</strong>'
);
});
}
li.innerHTML = htmlContent;
list.appendChild(li);
}
resultsDiv.appendChild(list);
} else {
const emptyMsg = document.createElement('p');
emptyMsg.textContent = 'No matching text to display.';
emptyMsg.style.margin = '0';
emptyMsg.style.color = '#94a3b8';
emptyMsg.style.fontStyle = 'italic';
resultsDiv.appendChild(emptyMsg);
}
container.appendChild(resultsDiv);
return container;
}
Apply Changes