realtime-ocr / index.html
podsni's picture
Add 2 files
0c63b0c verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Advanced Real-Time OCR Scanner</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/tesseract.js@4.1.1/dist/tesseract.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.video-container {
position: relative;
width: 100%;
max-width: 640px;
margin: 0 auto;
background: #111827;
border-radius: 12px;
overflow: hidden;
}
#video {
width: 100%;
height: auto;
transform: rotateY(180deg);
-webkit-transform: rotateY(180deg);
}
#canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: none;
}
.scan-box {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80%;
height: 30%;
border: 3px dashed rgba(59, 130, 246, 0.7);
border-radius: 8px;
pointer-events: none;
box-shadow: 0 0 0 1000px rgba(0, 0, 0, 0.5);
}
.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7);
}
70% {
box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
}
}
.result-text {
white-space: pre-wrap;
word-break: break-word;
}
.language-selector {
background-color: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(5px);
}
.preview-image {
max-height: 300px;
object-fit: contain;
border-radius: 8px;
margin: 0 auto;
display: block;
}
.tab-button {
transition: all 0.3s ease;
}
.tab-button.active {
background-color: #3b82f6;
color: white;
}
.progress-bar {
height: 4px;
background-color: #3b82f6;
transition: width 0.3s ease;
}
.dropzone {
border: 2px dashed #4b5563;
border-radius: 8px;
padding: 2rem;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
}
.dropzone.active {
border-color: #3b82f6;
background-color: rgba(59, 130, 246, 0.1);
}
.enhance-options {
transition: all 0.3s ease;
max-height: 0;
overflow: hidden;
}
.enhance-options.open {
max-height: 300px;
padding: 1rem 0;
}
</style>
</head>
<body class="bg-gray-900 text-white min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="text-center mb-8">
<h1 class="text-4xl font-bold mb-2 bg-gradient-to-r from-blue-400 to-purple-600 bg-clip-text text-transparent">
<i class="fas fa-camera-retro mr-2"></i> Advanced OCR Scanner
</h1>
<p class="text-gray-400 max-w-2xl mx-auto">
Extract text from camera or uploaded images with enhanced precision and editing tools
</p>
</header>
<!-- Tab Navigation -->
<div class="flex justify-center mb-8">
<div class="inline-flex rounded-full bg-gray-800 p-1">
<button id="cameraTab" class="tab-button px-6 py-2 rounded-full font-medium flex items-center active">
<i class="fas fa-camera mr-2"></i> Camera
</button>
<button id="uploadTab" class="tab-button px-6 py-2 rounded-full font-medium flex items-center">
<i class="fas fa-upload mr-2"></i> Upload
</button>
</div>
</div>
<div class="flex flex-col lg:flex-row gap-8">
<!-- Input Section -->
<div class="flex-1">
<div class="bg-gray-800 rounded-xl p-4 shadow-xl">
<!-- Camera Section -->
<div id="cameraSection">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold">
<i class="fas fa-video mr-2 text-blue-400"></i> Camera Feed
</h2>
<div class="flex items-center gap-2">
<div class="language-selector px-3 py-1 rounded-full">
<select id="language" class="bg-transparent text-white focus:outline-none">
<option value="eng">English</option>
<option value="spa">Spanish</option>
<option value="fra">French</option>
<option value="deu">German</option>
<option value="chi_sim">Chinese</option>
<option value="jpn">Japanese</option>
<option value="kor">Korean</option>
<option value="ara">Arabic</option>
</select>
</div>
<button id="enhanceBtn" class="bg-gray-700 hover:bg-gray-600 text-white px-3 py-1 rounded-full text-sm">
<i class="fas fa-sliders-h mr-1"></i> Enhance
</button>
</div>
</div>
<div class="enhance-options mb-4">
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm text-gray-400 mb-1">Brightness</label>
<input type="range" id="brightness" min="-100" max="100" value="0" class="w-full">
</div>
<div>
<label class="block text-sm text-gray-400 mb-1">Contrast</label>
<input type="range" id="contrast" min="-100" max="100" value="0" class="w-full">
</div>
<div>
<label class="block text-sm text-gray-400 mb-1">Threshold</label>
<input type="range" id="threshold" min="0" max="255" value="0" class="w-full">
</div>
<div>
<label class="block text-sm text-gray-400 mb-1">Sharpness</label>
<input type="range" id="sharpness" min="0" max="200" value="100" class="w-full">
</div>
</div>
</div>
<div class="video-container">
<video id="video" autoplay playsinline></video>
<canvas id="canvas"></canvas>
<div class="scan-box"></div>
</div>
<div class="flex justify-center mt-4 gap-4">
<button id="startBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-full font-medium flex items-center pulse">
<i class="fas fa-play mr-2"></i> Start Camera
</button>
<button id="captureBtn" class="bg-purple-600 hover:bg-purple-700 text-white px-6 py-2 rounded-full font-medium flex items-center opacity-50 cursor-not-allowed" disabled>
<i class="fas fa-camera mr-2"></i> Capture Text
</button>
</div>
</div>
<!-- Upload Section -->
<div id="uploadSection" class="hidden">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold">
<i class="fas fa-upload mr-2 text-blue-400"></i> Upload Image
</h2>
<div class="flex items-center gap-2">
<div class="language-selector px-3 py-1 rounded-full">
<select id="uploadLanguage" class="bg-transparent text-white focus:outline-none">
<option value="eng">English</option>
<option value="spa">Spanish</option>
<option value="fra">French</option>
<option value="deu">German</option>
<option value="chi_sim">Chinese</option>
<option value="jpn">Japanese</option>
<option value="kor">Korean</option>
<option value="ara">Arabic</option>
</select>
</div>
<button id="uploadEnhanceBtn" class="bg-gray-700 hover:bg-gray-600 text-white px-3 py-1 rounded-full text-sm">
<i class="fas fa-sliders-h mr-1"></i> Enhance
</button>
</div>
</div>
<div class="enhance-options mb-4">
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm text-gray-400 mb-1">Brightness</label>
<input type="range" id="uploadBrightness" min="-100" max="100" value="0" class="w-full">
</div>
<div>
<label class="block text-sm text-gray-400 mb-1">Contrast</label>
<input type="range" id="uploadContrast" min="-100" max="100" value="0" class="w-full">
</div>
<div>
<label class="block text-sm text-gray-400 mb-1">Threshold</label>
<input type="range" id="uploadThreshold" min="0" max="255" value="0" class="w-full">
</div>
<div>
<label class="block text-sm text-gray-400 mb-1">Sharpness</label>
<input type="range" id="uploadSharpness" min="0" max="200" value="100" class="w-full">
</div>
</div>
<button id="applyEnhanceBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-full text-sm mt-2">
<i class="fas fa-magic mr-1"></i> Apply Enhancements
</button>
</div>
<div class="dropzone" id="dropzone">
<i class="fas fa-cloud-upload-alt text-4xl text-blue-400 mb-2"></i>
<p class="font-medium">Drag & drop your image here</p>
<p class="text-sm text-gray-400 mt-1">or</p>
<label for="fileInput" class="inline-block bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-full mt-2 cursor-pointer">
<i class="fas fa-folder-open mr-1"></i> Browse Files
</label>
<input type="file" id="fileInput" accept="image/*" class="hidden">
</div>
<div id="imagePreviewContainer" class="hidden mt-4">
<div class="relative">
<img id="imagePreview" class="preview-image" src="" alt="Preview">
<canvas id="uploadCanvas" class="hidden"></canvas>
<div class="absolute top-2 right-2">
<button id="removeImageBtn" class="bg-red-600 hover:bg-red-700 text-white p-2 rounded-full">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<button id="processImageBtn" class="w-full bg-purple-600 hover:bg-purple-700 text-white px-6 py-2 rounded-full font-medium flex items-center justify-center mt-4">
<i class="fas fa-cogs mr-2"></i> Process Image
</button>
</div>
</div>
</div>
</div>
<!-- Results Section -->
<div class="flex-1">
<div class="bg-gray-800 rounded-xl p-4 shadow-xl h-full">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold">
<i class="fas fa-file-alt mr-2 text-green-400"></i> Extracted Text
</h2>
<div class="text-sm text-gray-400">
<span id="status">Status: Ready</span>
</div>
</div>
<div class="progress-bar-container bg-gray-700 rounded-full h-1 mb-4">
<div id="progressBar" class="progress-bar rounded-full" style="width: 0%"></div>
</div>
<div class="bg-gray-900 rounded-lg p-4 h-96 overflow-y-auto mb-4">
<div id="results" class="result-text text-gray-300">
<div class="text-center text-gray-500 py-16">
<i class="fas fa-align-left text-4xl mb-2"></i>
<p>Extracted text will appear here</p>
</div>
</div>
</div>
<div class="flex justify-between items-center">
<div class="text-sm text-gray-400">
<span id="confidence">Confidence: --</span>
</div>
<div class="flex gap-2">
<button id="copyBtn" class="bg-gray-700 hover:bg-gray-600 text-white px-4 py-2 rounded-full text-sm flex items-center" disabled>
<i class="fas fa-copy mr-1"></i> Copy
</button>
<button id="clearBtn" class="bg-gray-700 hover:bg-gray-600 text-white px-4 py-2 rounded-full text-sm flex items-center" disabled>
<i class="fas fa-trash-alt mr-1"></i> Clear
</button>
<button id="downloadBtn" class="bg-gray-700 hover:bg-gray-600 text-white px-4 py-2 rounded-full text-sm flex items-center" disabled>
<i class="fas fa-download mr-1"></i> Save
</button>
<button id="editBtn" class="bg-gray-700 hover:bg-gray-600 text-white px-4 py-2 rounded-full text-sm flex items-center" disabled>
<i class="fas fa-edit mr-1"></i> Edit
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Features Section -->
<div class="mt-12">
<h2 class="text-2xl font-bold text-center mb-6 bg-gradient-to-r from-blue-400 to-purple-600 bg-clip-text text-transparent">
<i class="fas fa-star mr-2"></i> Key Features
</h2>
<div class="grid md:grid-cols-3 gap-6">
<div class="bg-gray-800 rounded-xl p-6 hover:bg-gray-700 transition-all">
<div class="text-blue-400 text-3xl mb-4">
<i class="fas fa-camera"></i>
</div>
<h3 class="text-xl font-semibold mb-2">Real-Time Scanning</h3>
<p class="text-gray-400">Capture text instantly using your device's camera with auto-focus and enhancement tools.</p>
</div>
<div class="bg-gray-800 rounded-xl p-6 hover:bg-gray-700 transition-all">
<div class="text-purple-400 text-3xl mb-4">
<i class="fas fa-image"></i>
</div>
<h3 class="text-xl font-semibold mb-2">Image Upload</h3>
<p class="text-gray-400">Extract text from existing images in your gallery with drag & drop support.</p>
</div>
<div class="bg-gray-800 rounded-xl p-6 hover:bg-gray-700 transition-all">
<div class="text-green-400 text-3xl mb-4">
<i class="fas fa-magic"></i>
</div>
<h3 class="text-xl font-semibold mb-2">Image Enhancement</h3>
<p class="text-gray-400">Adjust brightness, contrast, threshold and sharpness for better OCR results.</p>
</div>
<div class="bg-gray-800 rounded-xl p-6 hover:bg-gray-700 transition-all">
<div class="text-yellow-400 text-3xl mb-4">
<i class="fas fa-language"></i>
</div>
<h3 class="text-xl font-semibold mb-2">Multi-Language</h3>
<p class="text-gray-400">Supports 8 languages including English, Spanish, Chinese, Arabic and more.</p>
</div>
<div class="bg-gray-800 rounded-xl p-6 hover:bg-gray-700 transition-all">
<div class="text-red-400 text-3xl mb-4">
<i class="fas fa-chart-line"></i>
</div>
<h3 class="text-xl font-semibold mb-2">Confidence Score</h3>
<p class="text-gray-400">See how confident the OCR engine is about each text extraction.</p>
</div>
<div class="bg-gray-800 rounded-xl p-6 hover:bg-gray-700 transition-all">
<div class="text-indigo-400 text-3xl mb-4">
<i class="fas fa-file-export"></i>
</div>
<h3 class="text-xl font-semibold mb-2">Export Options</h3>
<p class="text-gray-400">Copy, edit or download your extracted text for further use.</p>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// DOM Elements
const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const startBtn = document.getElementById('startBtn');
const captureBtn = document.getElementById('captureBtn');
const resultsDiv = document.getElementById('results');
const statusSpan = document.getElementById('status');
const confidenceSpan = document.getElementById('confidence');
const copyBtn = document.getElementById('copyBtn');
const clearBtn = document.getElementById('clearBtn');
const downloadBtn = document.getElementById('downloadBtn');
const editBtn = document.getElementById('editBtn');
const progressBar = document.getElementById('progressBar');
const languageSelect = document.getElementById('language');
const cameraTab = document.getElementById('cameraTab');
const uploadTab = document.getElementById('uploadTab');
const cameraSection = document.getElementById('cameraSection');
const uploadSection = document.getElementById('uploadSection');
const dropzone = document.getElementById('dropzone');
const fileInput = document.getElementById('fileInput');
const imagePreviewContainer = document.getElementById('imagePreviewContainer');
const imagePreview = document.getElementById('imagePreview');
const uploadCanvas = document.getElementById('uploadCanvas');
const removeImageBtn = document.getElementById('removeImageBtn');
const processImageBtn = document.getElementById('processImageBtn');
const uploadLanguage = document.getElementById('uploadLanguage');
const enhanceBtn = document.getElementById('enhanceBtn');
const uploadEnhanceBtn = document.getElementById('uploadEnhanceBtn');
const enhanceOptions = document.querySelector('#cameraSection .enhance-options');
const uploadEnhanceOptions = document.querySelector('#uploadSection .enhance-options');
const applyEnhanceBtn = document.getElementById('applyEnhanceBtn');
// State variables
let stream = null;
let isScanning = false;
let scanInterval = null;
let currentImageData = null;
// Tab switching
cameraTab.addEventListener('click', function() {
cameraTab.classList.add('active');
uploadTab.classList.remove('active');
cameraSection.classList.remove('hidden');
uploadSection.classList.add('hidden');
stopCamera(); // Stop camera when switching to upload tab
});
uploadTab.addEventListener('click', function() {
uploadTab.classList.add('active');
cameraTab.classList.remove('active');
cameraSection.classList.add('hidden');
uploadSection.classList.remove('hidden');
stopCamera(); // Stop camera when switching to upload tab
});
// Enhance options toggle
enhanceBtn.addEventListener('click', function() {
enhanceOptions.classList.toggle('open');
});
uploadEnhanceBtn.addEventListener('click', function() {
uploadEnhanceOptions.classList.toggle('open');
});
// Start camera
startBtn.addEventListener('click', async function() {
try {
if (stream) {
stopCamera();
return;
}
statusSpan.textContent = 'Status: Accessing camera...';
stream = await navigator.mediaDevices.getUserMedia({
video: {
facingMode: 'environment',
width: { ideal: 1280 },
height: { ideal: 720 }
},
audio: false
});
video.srcObject = stream;
startBtn.innerHTML = '<i class="fas fa-stop mr-2"></i> Stop Camera';
startBtn.classList.remove('bg-blue-600', 'hover:bg-blue-700');
startBtn.classList.add('bg-red-600', 'hover:bg-red-700');
captureBtn.disabled = false;
captureBtn.classList.remove('opacity-50', 'cursor-not-allowed');
statusSpan.textContent = 'Status: Camera ready';
// Auto-focus every 2 seconds (simulated)
scanInterval = setInterval(() => {
if (isScanning) return;
autoScan();
}, 2000);
} catch (err) {
console.error('Error accessing camera:', err);
statusSpan.textContent = 'Status: Error accessing camera';
showError(`Could not access camera: ${err.message}`);
}
});
function stopCamera() {
if (stream) {
stream.getTracks().forEach(track => track.stop());
stream = null;
video.srcObject = null;
startBtn.innerHTML = '<i class="fas fa-play mr-2"></i> Start Camera';
startBtn.classList.remove('bg-red-600', 'hover:bg-red-700');
startBtn.classList.add('bg-blue-600', 'hover:bg-blue-700');
captureBtn.disabled = true;
captureBtn.classList.add('opacity-50', 'cursor-not-allowed');
statusSpan.textContent = 'Status: Camera stopped';
clearInterval(scanInterval);
isScanning = false;
}
}
// Capture button
captureBtn.addEventListener('click', function() {
if (!stream) return;
captureFromCamera();
});
function captureFromCamera() {
isScanning = true;
statusSpan.textContent = 'Status: Processing image...';
captureBtn.disabled = true;
captureBtn.classList.add('opacity-50', 'cursor-not-allowed');
progressBar.style.width = '0%';
// Get enhancement values
const brightness = parseInt(document.getElementById('brightness').value);
const contrast = parseInt(document.getElementById('contrast').value);
const threshold = parseInt(document.getElementById('threshold').value);
const sharpness = parseInt(document.getElementById('sharpness').value) / 100;
// Get the dimensions of the scan box
const videoWidth = video.videoWidth;
const videoHeight = video.videoHeight;
const boxWidth = video.offsetWidth * 0.8;
const boxHeight = video.offsetHeight * 0.3;
const boxLeft = (video.offsetWidth - boxWidth) / 2;
const boxTop = (video.offsetHeight - boxHeight) / 2;
// Calculate the actual capture area in video coordinates
const scaleX = videoWidth / video.offsetWidth;
const scaleY = videoHeight / video.offsetHeight;
const captureWidth = boxWidth * scaleX;
const captureHeight = boxHeight * scaleY;
const captureLeft = boxLeft * scaleX;
const captureTop = boxTop * scaleY;
// Set canvas dimensions
canvas.width = captureWidth;
canvas.height = captureHeight;
// Draw video frame to canvas (only the scan box area)
const ctx = canvas.getContext('2d');
ctx.drawImage(
video,
captureLeft, captureTop, captureWidth, captureHeight,
0, 0, captureWidth, captureHeight
);
// Apply image enhancements
if (brightness !== 0 || contrast !== 0 || threshold > 0 || sharpness !== 1) {
applyImageEnhancements(ctx, canvas, brightness, contrast, threshold, sharpness);
}
// Get image data from canvas
const imageData = canvas.toDataURL('image/jpeg', 0.8);
currentImageData = imageData;
// Process with Tesseract
processImage(imageData, languageSelect.value);
}
// Auto-scan function
function autoScan() {
if (!stream || isScanning) return;
isScanning = true;
statusSpan.textContent = 'Status: Auto-scanning...';
// Get the dimensions of the scan box (same as manual capture)
const videoWidth = video.videoWidth;
const videoHeight = video.videoHeight;
const boxWidth = video.offsetWidth * 0.8;
const boxHeight = video.offsetHeight * 0.3;
const boxLeft = (video.offsetWidth - boxWidth) / 2;
const boxTop = (video.offsetHeight - boxHeight) / 2;
const scaleX = videoWidth / video.offsetWidth;
const scaleY = videoHeight / video.offsetHeight;
const captureWidth = boxWidth * scaleX;
const captureHeight = boxHeight * scaleY;
const captureLeft = boxLeft * scaleX;
const captureTop = boxTop * scaleY;
canvas.width = captureWidth;
canvas.height = captureHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(
video,
captureLeft, captureTop, captureWidth, captureHeight,
0, 0, captureWidth, captureHeight
);
const imageData = canvas.toDataURL('image/jpeg', 0.8);
currentImageData = imageData;
Tesseract.recognize(
imageData,
languageSelect.value,
{ logger: m => {} }
).then(({ data: { text, confidence } }) => {
if (text.trim()) {
resultsDiv.innerHTML = `
<div class="bg-blue-900/20 border border-blue-700 rounded p-3 mb-3 text-blue-100">
<i class="fas fa-robot mr-2"></i>
Auto-detected text! (Confidence: ${confidence.toFixed(1)}%)
</div>
<div class="result-text bg-gray-800 p-3 rounded">${text}</div>
`;
enableActionButtons();
confidenceSpan.textContent = `Confidence: ${confidence.toFixed(1)}%`;
}
statusSpan.textContent = 'Status: Ready (auto-scan)';
isScanning = false;
}).catch(err => {
console.error('Auto-scan error:', err);
isScanning = false;
statusSpan.textContent = 'Status: Ready (auto-scan failed)';
});
}
// File upload handling
fileInput.addEventListener('change', handleFileSelect);
// Drag and drop handling
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropzone.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
dropzone.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropzone.addEventListener(eventName, unhighlight, false);
});
function highlight() {
dropzone.classList.add('active');
}
function unhighlight() {
dropzone.classList.remove('active');
}
dropzone.addEventListener('drop', handleDrop, false);
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
if (files.length) {
handleFileSelect({ target: { files } });
}
}
function handleFileSelect(event) {
const file = event.target.files[0];
if (!file) return;
if (!file.type.match('image.*')) {
showError('Please select an image file (JPEG, PNG, etc.)');
return;
}
const reader = new FileReader();
reader.onload = function(e) {
imagePreview.src = e.target.result;
imagePreviewContainer.classList.remove('hidden');
dropzone.classList.add('hidden');
// Set up canvas for processing
const img = new Image();
img.onload = function() {
uploadCanvas.width = img.width;
uploadCanvas.height = img.height;
const ctx = uploadCanvas.getContext('2d');
ctx.drawImage(img, 0, 0);
currentImageData = uploadCanvas.toDataURL('image/jpeg', 0.8);
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
}
removeImageBtn.addEventListener('click', function() {
imagePreviewContainer.classList.add('hidden');
dropzone.classList.remove('hidden');
fileInput.value = '';
currentImageData = null;
});
processImageBtn.addEventListener('click', function() {
if (!currentImageData) return;
// Get enhancement values
const brightness = parseInt(document.getElementById('uploadBrightness').value);
const contrast = parseInt(document.getElementById('uploadContrast').value);
const threshold = parseInt(document.getElementById('uploadThreshold').value);
const sharpness = parseInt(document.getElementById('uploadSharpness').value) / 100;
// Apply enhancements if needed
if (brightness !== 0 || contrast !== 0 || threshold > 0 || sharpness !== 1) {
const ctx = uploadCanvas.getContext('2d');
const tempCanvas = document.createElement('canvas');
tempCanvas.width = uploadCanvas.width;
tempCanvas.height = uploadCanvas.height;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.drawImage(uploadCanvas, 0, 0);
applyImageEnhancements(tempCtx, tempCanvas, brightness, contrast, threshold, sharpness);
currentImageData = tempCanvas.toDataURL('image/jpeg', 0.8);
}
processImage(currentImageData, uploadLanguage.value);
});
applyEnhanceBtn.addEventListener('click', function() {
if (!currentImageData) return;
// Get enhancement values
const brightness = parseInt(document.getElementById('uploadBrightness').value);
const contrast = parseInt(document.getElementById('uploadContrast').value);
const threshold = parseInt(document.getElementById('uploadThreshold').value);
const sharpness = parseInt(document.getElementById('uploadSharpness').value) / 100;
// Apply to preview
const img = new Image();
img.onload = function() {
const tempCanvas = document.createElement('canvas');
tempCanvas.width = img.width;
tempCanvas.height = img.height;
const ctx = tempCanvas.getContext('2d');
ctx.drawImage(img, 0, 0);
applyImageEnhancements(ctx, tempCanvas, brightness, contrast, threshold, sharpness);
imagePreview.src = tempCanvas.toDataURL('image/jpeg', 0.8);
};
img.src = currentImageData;
});
function processImage(imageData, language) {
isScanning = true;
statusSpan.textContent = 'Status: Processing image...';
progressBar.style.width = '0%';
if (processImageBtn) {
processImageBtn.disabled = true;
processImageBtn.classList.add('opacity-50', 'cursor-not-allowed');
}
if (captureBtn) {
captureBtn.disabled = true;
captureBtn.classList.add('opacity-50', 'cursor-not-allowed');
}
Tesseract.recognize(
imageData,
language,
{
logger: m => {
if (m.status === 'recognizing text') {
statusSpan.textContent = `Status: ${m.status} (${Math.round(m.progress * 100)}%)`;
progressBar.style.width = `${m.progress * 100}%`;
} else {
statusSpan.textContent = `Status: ${m.status}`;
}
}
}
).then(({ data: { text, confidence, hocr } }) => {
if (text.trim()) {
resultsDiv.innerHTML = `
<div class="bg-green-900/20 border border-green-700 rounded p-3 mb-3 text-green-100">
<i class="fas fa-check-circle mr-2"></i>
Successfully extracted text! (Confidence: ${confidence.toFixed(1)}%)
</div>
<div class="result-text bg-gray-800 p-3 rounded">${text}</div>
`;
enableActionButtons();
confidenceSpan.textContent = `Confidence: ${confidence.toFixed(1)}%`;
} else {
resultsDiv.innerHTML = `
<div class="bg-yellow-900/20 border border-yellow-700 rounded p-3 text-yellow-100">
<i class="fas fa-exclamation-circle mr-2"></i>
No text was detected. Try adjusting the position or lighting.
</div>
`;
}
statusSpan.textContent = 'Status: Ready';
isScanning = false;
if (processImageBtn) {
processImageBtn.disabled = false;
processImageBtn.classList.remove('opacity-50', 'cursor-not-allowed');
}
if (captureBtn) {
captureBtn.disabled = false;
captureBtn.classList.remove('opacity-50', 'cursor-not-allowed');
}
}).catch(err => {
console.error('OCR Error:', err);
showError(`Error processing image: ${err.message}`);
statusSpan.textContent = 'Status: Error processing image';
isScanning = false;
if (processImageBtn) {
processImageBtn.disabled = false;
processImageBtn.classList.remove('opacity-50', 'cursor-not-allowed');
}
if (captureBtn) {
captureBtn.disabled = false;
captureBtn.classList.remove('opacity-50', 'cursor-not-allowed');
}
});
}
function applyImageEnhancements(ctx, canvas, brightness, contrast, threshold, sharpness) {
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
// Apply brightness and contrast
if (brightness !== 0 || contrast !== 0) {
const factor = (259 * (contrast + 255)) / (255 * (259 - contrast));
for (let i = 0; i < data.length; i += 4) {
// Apply contrast
data[i] = factor * (data[i] - 128) + 128 + brightness;
data[i+1] = factor * (data[i+1] - 128) + 128 + brightness;
data[i+2] = factor * (data[i+2] - 128) + 128 + brightness;
// Clamp values between 0-255
data[i] = Math.max(0, Math.min(255, data[i]));
data[i+1] = Math.max(0, Math.min(255, data[i+1]));
data[i+2] = Math.max(0, Math.min(255, data[i+2]));
}
}
// Apply threshold (convert to black and white)
if (threshold > 0) {
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i+1] + data[i+2]) / 3;
const value = avg > threshold ? 255 : 0;
data[i] = data[i+1] = data[i+2] = value;
}
}
ctx.putImageData(imageData, 0, 0);
// Apply sharpness (using a simple convolution filter)
if (sharpness !== 1) {
const tempCanvas = document.createElement('canvas');
tempCanvas.width = canvas.width;
tempCanvas.height = canvas.height;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.drawImage(canvas, 0, 0);
// Apply sharpening filter
const weights = [0, -1 * sharpness, 0, -1 * sharpness, 1 + 4 * sharpness, -1 * sharpness, 0, -1 * sharpness, 0];
const divisor = 1;
const bias = 0;
ctx.filter = `contrast(${100 + contrast}%) brightness(${100 + brightness}%)`;
ctx.drawImage(tempCanvas, 0, 0);
}
}
function enableActionButtons() {
copyBtn.disabled = false;
clearBtn.disabled = false;
downloadBtn.disabled = false;
editBtn.disabled = false;
}
function showError(message) {
resultsDiv.innerHTML = `
<div class="bg-red-900/50 border border-red-700 rounded p-3 text-red-100">
<i class="fas fa-exclamation-triangle mr-2"></i>
${message}
</div>
`;
}
// Copy text
copyBtn.addEventListener('click', function() {
const textToCopy = resultsDiv.querySelector('.result-text')?.textContent;
if (textToCopy) {
navigator.clipboard.writeText(textToCopy).then(() => {
const originalText = copyBtn.innerHTML;
copyBtn.innerHTML = '<i class="fas fa-check mr-1"></i> Copied!';
setTimeout(() => {
copyBtn.innerHTML = originalText;
}, 2000);
});
}
});
// Clear results
clearBtn.addEventListener('click', function() {
resultsDiv.innerHTML = `
<div class="text-center text-gray-500 py-16">
<i class="fas fa-align-left text-4xl mb-2"></i>
<p>Extracted text will appear here</p>
</div>
`;
copyBtn.disabled = true;
clearBtn.disabled = true;
downloadBtn.disabled = true;
editBtn.disabled = true;
confidenceSpan.textContent = 'Confidence: --';
});
// Download text
downloadBtn.addEventListener('click', function() {
const textToDownload = resultsDiv.querySelector('.result-text')?.textContent;
if (textToDownload) {
const blob = new Blob([textToDownload], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `ocr-extracted-text-${new Date().toISOString().slice(0,10)}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
});
// Edit text
editBtn.addEventListener('click', function() {
const resultText = resultsDiv.querySelector('.result-text');
if (!resultText) return;
const currentText = resultText.textContent;
resultText.innerHTML = `
<textarea id="textEditor" class="w-full h-64 bg-gray-700 text-white p-3 rounded">${currentText}</textarea>
<div class="flex justify-end mt-2 gap-2">
<button id="cancelEditBtn" class="bg-gray-600 hover:bg-gray-500 text-white px-4 py-1 rounded text-sm">
Cancel
</button>
<button id="saveEditBtn" class="bg-blue-600 hover:bg-blue-500 text-white px-4 py-1 rounded text-sm">
Save Changes
</button>
</div>
`;
document.getElementById('cancelEditBtn').addEventListener('click', function() {
resultText.textContent = currentText;
});
document.getElementById('saveEditBtn').addEventListener('click', function() {
const editedText = document.getElementById('textEditor').value;
resultText.textContent = editedText;
});
});
// Language change
languageSelect.addEventListener('change', function() {
statusSpan.textContent = `Status: Language set to ${this.options[this.selectedIndex].text}`;
});
uploadLanguage.addEventListener('change', function() {
statusSpan.textContent = `Status: Language set to ${this.options[this.selectedIndex].text}`;
});
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=podsni/realtime-ocr" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>