| Server IP : 127.0.0.1 / Your IP : 216.73.216.48 Web Server : Apache/2.4.58 (Win64) OpenSSL/3.1.3 PHP/8.2.12 System : Windows NT DESKTOP-3H4FHQJ 10.0 build 19045 (Windows 10) AMD64 User : win 10 ( 0) PHP Version : 8.2.12 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : OFF | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : D:/xampp/htdocs-coblaa/CB_image_text/ |
Upload File : |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Image Text Extractor</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
background-color: #f3f4f6;
}
.container {
min-height: 100vh;
}
.loading-spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #3b82f6;
border-radius: 50%;
width: 32px;
height: 32px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body class="bg-gray-100 p-4 sm:p-8">
<div class="container mx-auto max-w-4xl bg-white rounded-3xl shadow-2xl overflow-hidden flex flex-col items-center p-8 space-y-8">
<!-- Header -->
<div class="text-center space-y-2">
<h1 class="text-4xl font-extrabold text-gray-900">Image Text Extractor</h1>
<p class="text-lg text-gray-500">Upload an image to extract text from it.</p>
</div>
<!-- File Input and Image Display -->
<div class="w-full flex flex-col md:flex-row gap-8 items-center">
<!-- File Input Area -->
<div class="w-full md:w-1/2 flex flex-col items-center justify-center p-8 bg-gray-50 border-2 border-dashed border-gray-300 rounded-2xl transition-all duration-300 hover:border-blue-500 hover:bg-blue-50 cursor-pointer group transform hover:scale-105">
<label for="image-upload" class="flex flex-col items-center justify-center w-full h-full">
<svg class="w-20 h-20 text-gray-400 mx-auto transition-colors duration-300 group-hover:text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
<p class="mt-4 text-sm text-gray-600 text-center font-medium transition-colors duration-300 group-hover:text-blue-700">Click to upload or drag and drop</p>
<p class="text-xs text-gray-400 text-center">JPG, PNG, GIF up to 10MB</p>
<input id="image-upload" type="file" accept="image/*" class="hidden">
</label>
</div>
<!-- Image Preview -->
<div id="image-preview-container" class="w-full md:w-1/2 flex items-center justify-center p-4 bg-gray-50 rounded-2xl border border-gray-200" style="display: none;">
<img id="uploaded-image" src="#" alt="Image preview" class="max-h-64 max-w-full h-auto rounded-xl shadow-md object-contain">
</div>
</div>
<!-- Output Area -->
<div id="output-area" class="w-full" style="display: none;">
<div class="flex flex-col space-y-6">
<!-- Status/Loading -->
<div id="status-message" class="text-center font-medium text-lg text-gray-600" style="display: none;">
<div class="flex items-center justify-center space-x-2">
<div class="loading-spinner"></div>
<span>Extracting text...</span>
</div>
</div>
<!-- Extracted Text -->
<div class="w-full">
<textarea id="extracted-text" class="w-full h-64 p-4 text-gray-800 border-2 border-gray-300 rounded-xl resize-y focus:outline-none focus:ring-4 focus:ring-blue-500 focus:border-blue-500 transition-all duration-300" readonly placeholder="Extracted text will appear here..."></textarea>
</div>
<!-- Action Button -->
<button id="copy-button" class="w-full py-3 px-6 bg-blue-600 text-white font-bold rounded-xl shadow-lg transition-all duration-300 transform hover:scale-105 hover:bg-blue-700 focus:outline-none focus:ring-4 focus:ring-blue-500 focus:ring-offset-2 disabled:bg-gray-400 disabled:cursor-not-allowed">
Copy Text
</button>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const imageUpload = document.getElementById('image-upload');
const uploadedImage = document.getElementById('uploaded-image');
const imagePreviewContainer = document.getElementById('image-preview-container');
const outputArea = document.getElementById('output-area');
const extractedTextarea = document.getElementById('extracted-text');
const copyButton = document.getElementById('copy-button');
const statusMessage = document.getElementById('status-message');
// API key is handled by the environment, leave as empty string
const apiKey = "";
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-05-20:generateContent?key=${apiKey}`;
imageUpload.addEventListener('change', handleImageUpload);
copyButton.addEventListener('click', copyToClipboard);
async function handleImageUpload(event) {
const file = event.target.files[0];
if (!file) return;
// Clear previous results
extractedTextarea.value = '';
imagePreviewContainer.style.display = 'none';
outputArea.style.display = 'block';
copyButton.disabled = true;
displayMessage('Extracting text...', false);
const reader = new FileReader();
reader.onload = async (e) => {
const dataUrl = e.target.result;
const base64Data = dataUrl.split(',')[1];
const mimeType = file.type;
uploadedImage.src = dataUrl;
imagePreviewContainer.style.display = 'flex';
try {
const text = await extractTextFromImage(base64Data, mimeType);
extractedTextarea.value = text;
copyButton.disabled = false;
displayMessage('', false); // Clear message
} catch (error) {
console.error('Error:', error);
displayMessage(`Error: ${error.message || 'Failed to extract text. Please try again.'}`, true);
copyButton.disabled = true;
}
};
reader.onerror = (error) => {
console.error('FileReader error:', error);
displayMessage('Error: Could not read file.', true);
copyButton.disabled = true;
};
reader.readAsDataURL(file);
}
function displayMessage(message, isError) {
statusMessage.style.display = 'flex';
if (isError) {
statusMessage.innerHTML = `<span class="text-red-500">${message}</span>`;
} else {
statusMessage.innerHTML = `<div class="flex items-center justify-center space-x-2"><div class="loading-spinner"></div><span>${message}</span></div>`;
}
if (message === '') {
statusMessage.style.display = 'none';
}
}
async function extractTextFromImage(base64ImageData, mimeType) {
const prompt = "Please extract all text from this image and return it as a single block of text. Do not add any introductory or concluding remarks.";
const payload = {
contents: [
{
role: "user",
parts: [
{ text: prompt },
{
inlineData: {
mimeType: mimeType,
data: base64ImageData
}
}
]
}
],
};
// Using exponential backoff for API calls
const maxRetries = 5;
let retryCount = 0;
while (retryCount < maxRetries) {
try {
const response = await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`API error: ${response.status} ${response.statusText}`);
}
const result = await response.json();
console.log('API response:', result);
// Check for valid response structure
if (result.candidates && result.candidates.length > 0 &&
result.candidates[0].content && result.candidates[0].content.parts &&
result.candidates[0].content.parts.length > 0) {
return result.candidates[0].content.parts[0].text;
} else {
throw new Error("Invalid API response structure or empty content.");
}
} catch (error) {
retryCount++;
console.error(`Retry attempt ${retryCount}:`, error);
if (retryCount >= maxRetries) {
throw error; // Re-throw if max retries are reached
}
const delay = Math.pow(2, retryCount) * 1000; // Exponential backoff
await new Promise(res => setTimeout(res, delay));
}
}
return ""; // Should not be reached
}
function copyToClipboard() {
extractedTextarea.select();
extractedTextarea.setSelectionRange(0, 99999); // For mobile devices
try {
document.execCommand('copy');
const originalText = copyButton.textContent;
copyButton.textContent = "Copied!";
setTimeout(() => {
copyButton.textContent = originalText;
}, 2000);
} catch (err) {
console.error('Failed to copy text: ', err);
}
}
});
</script>
</body>
</html>