You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, language = 'eng', enhanceMode = 'Grayscale', contrast = '1.2', illuminanceSmoothing = '15') {
// Create the main wrapper element
const container = document.createElement('div');
container.style.display = 'flex';
container.style.flexDirection = 'column';
container.style.gap = '20px';
container.style.fontFamily = 'system-ui, -apple-system, sans-serif';
container.style.width = '100%';
container.style.padding = '20px';
container.style.boxSizing = 'border-box';
container.style.backgroundColor = '#f0f2f5';
container.style.borderRadius = '12px';
const headerTitle = document.createElement('h2');
headerTitle.textContent = "Light Bill Data Extractor";
headerTitle.style.margin = '0 0 10px 0';
headerTitle.style.color = '#333';
headerTitle.style.fontSize = '22px';
container.appendChild(headerTitle);
const splitView = document.createElement('div');
splitView.style.display = 'flex';
splitView.style.flexWrap = 'wrap';
splitView.style.gap = '20px';
splitView.style.width = '100%';
// Visual extraction (Document Scanner filter layout)
const canvasWrapper = document.createElement('div');
canvasWrapper.style.flex = '1 1 320px';
canvasWrapper.style.display = 'flex';
canvasWrapper.style.flexDirection = 'column';
canvasWrapper.style.backgroundColor = '#fff';
canvasWrapper.style.padding = '15px';
canvasWrapper.style.borderRadius = '8px';
canvasWrapper.style.boxShadow = '0 2px 5px rgba(0,0,0,0.05)';
const canvasHeader = document.createElement('div');
canvasHeader.innerHTML = '<strong style="color: #222;">Enhanced Document Image</strong><br><small style="color: #666;">Illumination flattened for clear reading</small>';
canvasHeader.style.marginBottom = '12px';
canvasHeader.style.lineHeight = '1.4';
canvasWrapper.appendChild(canvasHeader);
// Context & Canvas setup
const canvas = document.createElement('canvas');
const width = originalImg.width;
const height = originalImg.height;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d', { willReadFrequently: true });
// Draw original image
ctx.drawImage(originalImg, 0, 0);
const origData = ctx.getImageData(0, 0, width, height).data;
// Create computationally efficient illumination map (Background Subtraction)
// Scale image down heavily for fast and smooth glowing blur
const scaleDown = Math.min(1, 500 / Math.max(width, height));
const blurCanvas = document.createElement('canvas');
blurCanvas.width = width * scaleDown;
blurCanvas.height = height * scaleDown;
const blurCtx = blurCanvas.getContext('2d');
// Apply blur relative to scaled canvas context
blurCtx.filter = `blur(${parseFloat(illuminanceSmoothing)}px)`;
blurCtx.drawImage(originalImg, 0, 0, blurCanvas.width, blurCanvas.height);
// Upscale the smoothed illumination map cleanly
const bigBlurCanvas = document.createElement('canvas');
bigBlurCanvas.width = width;
bigBlurCanvas.height = height;
const bigBlurCtx = bigBlurCanvas.getContext('2d', { willReadFrequently: true });
bigBlurCtx.imageSmoothingEnabled = true;
bigBlurCtx.imageSmoothingQuality = "high";
bigBlurCtx.drawImage(blurCanvas, 0, 0, width, height);
const blurData = bigBlurCtx.getImageData(0, 0, width, height).data;
const resultImgData = ctx.createImageData(width, height);
const resultData = resultImgData.data;
const bWhiteThresh = 210;
const bBlackThresh = 110;
const cVal = parseFloat(contrast);
// Apply adaptive division filter
for (let i = 0; i < origData.length; i += 4) {
let r = origData[i], g = origData[i + 1], b = origData[i + 2];
let br = blurData[i] || 1, bg = blurData[i + 1] || 1, bb = blurData[i + 2] || 1;
// Normalize against illumination map to remove shadowing
let nr = (r / br) * 230;
let ng = (g / bg) * 230;
let nb = (b / bb) * 230;
// Apply target contrast
nr = ((nr / 255 - 0.5) * cVal + 0.5) * 255;
ng = ((ng / 255 - 0.5) * cVal + 0.5) * 255;
nb = ((nb / 255 - 0.5) * cVal + 0.5) * 255;
let finalR = Math.min(255, Math.max(0, nr));
let finalG = Math.min(255, Math.max(0, ng));
let finalB = Math.min(255, Math.max(0, nb));
// Convert to Luminance (Grayscale)
const luma = finalR * 0.299 + finalG * 0.587 + finalB * 0.114;
if (enhanceMode === 'B&W') {
let finalVal;
if (luma > bWhiteThresh) finalVal = 255;
else if (luma < bBlackThresh) finalVal = 0;
else finalVal = ((luma - bBlackThresh) / (bWhiteThresh - bBlackThresh)) * 255;
resultData[i] = finalVal;
resultData[i+1] = finalVal;
resultData[i+2] = finalVal;
} else {
resultData[i] = luma;
resultData[i+1] = luma;
resultData[i+2] = luma;
}
resultData[i + 3] = origData[i + 3];
}
ctx.putImageData(resultImgData, 0, 0);
// Style and inject canvas
canvas.style.width = '100%';
canvas.style.height = 'auto';
canvas.style.border = '1px solid #e1e4e8';
canvas.style.borderRadius = '4px';
canvasWrapper.appendChild(canvas);
// Text extraction (OCR section)
const txtWrapper = document.createElement('div');
txtWrapper.style.flex = '1 1 320px';
txtWrapper.style.display = 'flex';
txtWrapper.style.flexDirection = 'column';
txtWrapper.style.backgroundColor = '#fff';
txtWrapper.style.padding = '15px';
txtWrapper.style.borderRadius = '8px';
txtWrapper.style.boxShadow = '0 2px 5px rgba(0,0,0,0.05)';
const txtHeader = document.createElement('div');
txtHeader.innerHTML = '<strong style="color: #222;">Extracted Bill Data (OCR)</strong><br><small style="color: #666;">Reading enhanced image data automatically</small>';
txtHeader.style.marginBottom = '12px';
txtHeader.style.lineHeight = '1.4';
txtWrapper.appendChild(txtHeader);
const textOutput = document.createElement('textarea');
textOutput.style.flexGrow = '1';
textOutput.style.width = '100%';
textOutput.style.minHeight = '250px';
textOutput.style.padding = '12px';
textOutput.style.border = '1px solid #e1e4e8';
textOutput.style.borderRadius = '6px';
textOutput.style.boxSizing = 'border-box';
textOutput.style.fontSize = '14px';
textOutput.style.fontFamily = 'monospace';
textOutput.style.color = '#333';
textOutput.style.resize = 'vertical';
textOutput.style.backgroundColor = '#fafafa';
textOutput.value = "Initializing extraction engine...\nThis may take a moment to download library files.";
txtWrapper.appendChild(textOutput);
splitView.appendChild(canvasWrapper);
splitView.appendChild(txtWrapper);
container.appendChild(splitView);
// Dynamically Load Tesseract.js (v5) and process text asynchronously
const loadTesseract = (src) => new Promise((resolve, reject) => {
if (window.Tesseract) { resolve(); return; }
const script = document.createElement('script');
script.src = src;
script.crossOrigin = "anonymous";
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
loadTesseract('https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js')
.then(() => {
textOutput.value = "OCR library initialized. Processing image...";
return Tesseract.recognize(
canvas,
language,
{
logger: m => {
if (m.status) {
let progressMsg = m.progress ? ` (${Math.round(m.progress * 100)}%)` : '';
textOutput.value = `Status: ${m.status}${progressMsg}\nWorking...`;
}
}
}
);
})
.then(({ data: { text } }) => {
if (text.trim()) {
textOutput.value = text;
textOutput.style.backgroundColor = '#fff';
} else {
textOutput.value = "Extraction finished, but no actionable text was found in the image.";
}
})
.catch(err => {
textOutput.value = "Error during text extraction:\n" + err.message;
textOutput.style.color = '#d32f2f';
});
return container;
}
Apply Changes