| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>AI Image Enhancer</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
|
|
| <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@latest/dist/tf.min.js"></script> |
|
|
| <style> |
| |
| body { |
| font-family: sans-serif; |
| } |
| canvas { |
| max-width: 100%; |
| height: auto; |
| border: 1px solid #ccc; |
| } |
| .loader { |
| border: 5px solid #f3f3f3; |
| border-top: 5px solid #3498db; |
| border-radius: 50%; |
| width: 40px; |
| height: 40px; |
| animation: spin 1s linear infinite; |
| margin: 20px auto; |
| } |
| @keyframes spin { |
| 0% { transform: rotate(0deg); } |
| 100% { transform: rotate(360deg); } |
| } |
| </style> |
| </head> |
| <body class="bg-gray-100 p-8"> |
|
|
| <div class="container mx-auto max-w-4xl bg-white p-6 rounded-lg shadow-lg"> |
|
|
| <h1 class="text-3xl font-bold mb-6 text-center text-blue-600">AI Image Enhancer</h1> |
| <p class="text-center text-gray-600 mb-6">Increase resolution, retouch, denoise, and more using TensorFlow.js.</p> |
|
|
| <div class="mb-6 p-4 border rounded-md bg-gray-50"> |
| <label for="imageUpload" class="block text-lg font-medium text-gray-700 mb-2">1. Upload Image:</label> |
| <input type="file" id="imageUpload" accept="image/*" class="block w-full text-sm text-gray-500 |
| file:mr-4 file:py-2 file:px-4 |
| file:rounded-full file:border-0 |
| file:text-sm file:font-semibold |
| file:bg-blue-50 file:text-blue-700 |
| hover:file:bg-blue-100 |
| "/> |
| <p id="uploadError" class="text-red-500 text-sm mt-2"></p> |
| </div> |
|
|
| <div class="mb-6 p-4 border rounded-md bg-gray-50"> |
| <label for="enhancementType" class="block text-lg font-medium text-gray-700 mb-2">2. Select Enhancement:</label> |
| <select id="enhancementType" class="block w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"> |
| <option value="upscale">Increase Resolution (Upscale 2x)</option> |
| <option value="denoise">Denoise (Basic)</option> |
| <option value="retouch">Retouch (Simple Filter)</option> |
| </select> |
| </div> |
|
|
| <div class="text-center mb-6"> |
| <button id="enhanceButton" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-full text-lg disabled:opacity-50 disabled:cursor-not-allowed" disabled> |
| Enhance Image |
| </button> |
| </div> |
|
|
| <div id="status" class="text-center text-gray-600 mb-4 h-10"></div> |
| <div id="loader" class="loader hidden"></div> |
|
|
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> |
| <div> |
| <h2 class="text-xl font-semibold mb-2 text-center">Original Image</h2> |
| <canvas id="originalCanvas"></canvas> |
| </div> |
| <div> |
| <h2 class="text-xl font-semibold mb-2 text-center">Enhanced Image</h2> |
| <canvas id="enhancedCanvas"></canvas> |
| </div> |
| </div> |
|
|
| <div class="mt-8 text-center text-xs text-gray-500"> |
| <p>Powered by TensorFlow.js</p> |
| <p>MIT License - [Your Name/Org] 2025</p> |
| </div> |
| </div> |
|
|
| <script> |
| |
| const imageUpload = document.getElementById('imageUpload'); |
| const enhanceButton = document.getElementById('enhanceButton'); |
| const enhancementType = document.getElementById('enhancementType'); |
| const originalCanvas = document.getElementById('originalCanvas'); |
| const enhancedCanvas = document.getElementById('enhancedCanvas'); |
| const statusDiv = document.getElementById('status'); |
| const loader = document.getElementById('loader'); |
| const uploadError = document.getElementById('uploadError'); |
| |
| const originalCtx = originalCanvas.getContext('2d'); |
| const enhancedCtx = enhancedCanvas.getContext('2d'); |
| |
| let originalImage = null; |
| let model = null; |
| |
| |
| imageUpload.addEventListener('change', handleImageUpload); |
| enhanceButton.addEventListener('click', handleEnhancement); |
| |
| |
| |
| async function handleImageUpload(event) { |
| const file = event.target.files[0]; |
| uploadError.textContent = ''; |
| enhanceButton.disabled = true; |
| originalImage = null; |
| originalCtx.clearRect(0, 0, originalCanvas.width, originalCanvas.height); |
| enhancedCtx.clearRect(0, 0, enhancedCanvas.width, enhancedCanvas.height); |
| |
| |
| if (!file || !file.type.startsWith('image/')) { |
| uploadError.textContent = 'Please select a valid image file.'; |
| return; |
| } |
| |
| statusDiv.textContent = 'Loading image...'; |
| try { |
| originalImage = await loadImageFromFile(file); |
| displayImageOnCanvas(originalImage, originalCanvas, originalCtx); |
| statusDiv.textContent = 'Image loaded. Ready to enhance.'; |
| enhanceButton.disabled = false; |
| } catch (error) { |
| console.error("Error loading image:", error); |
| uploadError.textContent = 'Could not load the image.'; |
| statusDiv.textContent = ''; |
| } |
| } |
| |
| function loadImageFromFile(file) { |
| return new Promise((resolve, reject) => { |
| const reader = new FileReader(); |
| reader.onload = (e) => { |
| const img = new Image(); |
| img.onload = () => resolve(img); |
| img.onerror = reject; |
| img.src = e.target.result; |
| }; |
| reader.onerror = reject; |
| reader.readAsDataURL(file); |
| }); |
| } |
| |
| function displayImageOnCanvas(img, canvas, ctx) { |
| |
| canvas.width = img.naturalWidth; |
| canvas.height = img.naturalHeight; |
| ctx.drawImage(img, 0, 0, canvas.width, canvas.height); |
| console.log(`Displayed original image (${canvas.width}x${canvas.height})`); |
| } |
| |
| async function handleEnhancement() { |
| if (!originalImage) { |
| statusDiv.textContent = 'Please upload an image first.'; |
| return; |
| } |
| |
| const selectedTask = enhancementType.value; |
| statusDiv.textContent = `Starting ${selectedTask}...`; |
| loader.classList.remove('hidden'); |
| enhanceButton.disabled = true; |
| enhancedCtx.clearRect(0, 0, enhancedCanvas.width, enhancedCanvas.height); |
| |
| try { |
| |
| |
| await runAIEnhancement(selectedTask, originalCanvas, enhancedCanvas, enhancedCtx); |
| statusDiv.textContent = 'Enhancement complete!'; |
| console.log("Enhancement successful."); |
| |
| } catch (error) { |
| console.error(`Error during ${selectedTask}:`, error); |
| statusDiv.textContent = `Error during enhancement: ${error.message || error}`; |
| } finally { |
| loader.classList.add('hidden'); |
| enhanceButton.disabled = false; |
| } |
| } |
| |
| async function runAIEnhancement(task, sourceCanvas, targetCanvas, targetCtx) { |
| console.log(`Running task: ${task}`); |
| |
| |
| await tf.ready(); |
| console.log(`Using TFJS backend: ${tf.getBackend()}`); |
| |
| |
| |
| const inputTensor = tf.browser.fromPixels(sourceCanvas); |
| console.log("Input tensor shape:", inputTensor.shape); |
| |
| let outputTensor; |
| |
| |
| |
| |
| |
| |
| |
| statusDiv.textContent = 'Loading AI model... (Placeholder)'; |
| |
| await new Promise(resolve => setTimeout(resolve, 500)); |
| |
| statusDiv.textContent = 'Processing image... (Placeholder)'; |
| |
| if (task === 'upscale') { |
| |
| |
| |
| console.log("Simulating upscale..."); |
| const scale = 1.5; |
| targetCanvas.width = Math.round(sourceCanvas.width * scale); |
| targetCanvas.height = Math.round(sourceCanvas.height * scale); |
| targetCtx.drawImage(sourceCanvas, 0, 0, targetCanvas.width, targetCanvas.height); |
| |
| |
| } else if (task === 'denoise') { |
| |
| |
| |
| console.log("Simulating denoise..."); |
| targetCanvas.width = sourceCanvas.width; |
| targetCanvas.height = sourceCanvas.height; |
| targetCtx.filter = 'blur(1px)'; |
| targetCtx.drawImage(sourceCanvas, 0, 0); |
| targetCtx.filter = 'none'; |
| |
| |
| } else if (task === 'retouch') { |
| |
| |
| console.log("Simulating retouch (sepia filter)..."); |
| targetCanvas.width = sourceCanvas.width; |
| targetCanvas.height = sourceCanvas.height; |
| targetCtx.filter = 'sepia(60%)'; |
| targetCtx.drawImage(sourceCanvas, 0, 0); |
| targetCtx.filter = 'none'; |
| |
| |
| } else { |
| console.warn("Unknown enhancement task:", task); |
| |
| targetCanvas.width = sourceCanvas.width; |
| targetCanvas.height = sourceCanvas.height; |
| targetCtx.drawImage(sourceCanvas, 0, 0); |
| outputTensor = inputTensor.clone(); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (inputTensor && !inputTensor.isDisposed) { |
| inputTensor.dispose(); |
| console.log("Disposed input tensor."); |
| } |
| } |
| |
| |
| statusDiv.textContent = 'Ready. Please upload an image.'; |
| |
| </script> |
|
|
| </body> |
| </html> |
|
|