| | <!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> |
| |
|