| <!DOCTYPE html> |
| <html lang="en"> |
|
|
| <head> |
| <meta charset="UTF-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| <title>DeOldify Quantized (Browser)</title> |
| <script src="https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js"></script> |
| <style> |
| body { |
| font-family: sans-serif; |
| max-width: 800px; |
| margin: 0 auto; |
| padding: 20px; |
| } |
| |
| h1 { |
| text-align: center; |
| } |
| |
| .container { |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| gap: 20px; |
| } |
| |
| canvas { |
| border: 1px solid #ccc; |
| max-width: 100%; |
| } |
| |
| .controls { |
| margin-bottom: 20px; |
| } |
| |
| #status { |
| font-weight: bold; |
| margin-top: 10px; |
| } |
| </style> |
| </head> |
|
|
| <body> |
| <h1>DeOldify Quantized Model</h1> |
| <p style="text-align: center;">Faster, smaller download (61MB), slightly lower quality.</p> |
| <div class="container"> |
| <div class="controls"> |
| <input type="file" id="imageInput" accept="image/*" /> |
| </div> |
| <div id="status">Select an image to start...</div> |
| <canvas id="outputCanvas"></canvas> |
| </div> |
|
|
| <script> |
| const MODEL_URL = "https://huggingface.co/thookham/DeOldify-on-Browser/resolve/main/deoldify-quant.onnx"; |
| let session = null; |
| |
| const preprocess = (input_imageData, width, height) => { |
| const floatArr = new Float32Array(width * height * 3); |
| let j = 0; |
| for (let i = 0; i < input_imageData.data.length; i += 4) { |
| |
| floatArr[j] = input_imageData.data[i] / 255.0; |
| floatArr[j + 1] = input_imageData.data[i + 1] / 255.0; |
| floatArr[j + 2] = input_imageData.data[i + 2] / 255.0; |
| j += 3; |
| } |
| return floatArr; |
| }; |
| |
| const postprocess = (tensor) => { |
| const channels = tensor.dims[1]; |
| const height = tensor.dims[2]; |
| const width = tensor.dims[3]; |
| const imageData = new ImageData(width, height); |
| const data = imageData.data; |
| const tensorData = new Float32Array(tensor.data); |
| |
| for (let h = 0; h < height; h++) { |
| for (let w = 0; w < width; w++) { |
| let rgb = []; |
| for (let c = 0; c < channels; c++) { |
| const tensorIndex = (c * height + h) * width + w; |
| const value = tensorData[tensorIndex]; |
| |
| let val = value * 255.0; |
| if (val < 0) val = 0; |
| if (val > 255) val = 255; |
| rgb.push(Math.round(val)); |
| } |
| data[(h * width + w) * 4] = rgb[0]; |
| data[(h * width + w) * 4 + 1] = rgb[1]; |
| data[(h * width + w) * 4 + 2] = rgb[2]; |
| data[(h * width + w) * 4 + 3] = 255; |
| } |
| } |
| return imageData; |
| }; |
| |
| async function init() { |
| const status = document.getElementById('status'); |
| status.innerText = "Checking cache..."; |
| try { |
| let buffer; |
| const cacheName = 'deoldify-models-v1'; |
| |
| |
| try { |
| const cache = await caches.open(cacheName); |
| const cachedResponse = await cache.match(MODEL_URL); |
| |
| if (cachedResponse) { |
| status.innerText = "Loading model from cache..."; |
| const blob = await cachedResponse.blob(); |
| buffer = await blob.arrayBuffer(); |
| } |
| } catch (e) { |
| console.warn("Cache API not supported or failed:", e); |
| } |
| |
| |
| if (!buffer) { |
| status.innerText = "Downloading model from Hugging Face... 0%"; |
| const response = await fetch(MODEL_URL); |
| if (!response.ok) throw new Error(`Failed to fetch model: ${response.statusText}`); |
| |
| const contentLength = response.headers.get('content-length'); |
| const total = contentLength ? parseInt(contentLength, 10) : 0; |
| let loaded = 0; |
| |
| const reader = response.body.getReader(); |
| const chunks = []; |
| |
| while (true) { |
| const { done, value } = await reader.read(); |
| if (done) break; |
| chunks.push(value); |
| loaded += value.length; |
| if (total) { |
| const progress = Math.round((loaded / total) * 100); |
| status.innerText = `Downloading model from Hugging Face... ${progress}%`; |
| } else { |
| status.innerText = `Downloading model from Hugging Face... ${(loaded / 1024 / 1024).toFixed(1)} MB`; |
| } |
| } |
| |
| const blob = new Blob(chunks); |
| buffer = await blob.arrayBuffer(); |
| |
| |
| try { |
| const cache = await caches.open(cacheName); |
| await cache.put(MODEL_URL, new Response(blob)); |
| console.log("Model saved to cache"); |
| } catch (e) { |
| console.warn("Failed to save to cache:", e); |
| } |
| } |
| |
| status.innerText = "Initializing session..."; |
| session = await ort.InferenceSession.create(buffer); |
| |
| status.innerText = "Model loaded! Select an image."; |
| console.log("Session created:", session); |
| } catch (e) { |
| status.innerText = "Error loading model: " + e.message; |
| console.error(e); |
| if (e.message.includes("Failed to fetch")) { |
| status.innerHTML += "<br><br>⚠️ <b>CORS Error Detected</b>: If you are running this file directly (file://), you must use a local server.<br>Run <code>python -m http.server 8000</code> in the terminal and visit <code>http://localhost:8000/quantized.html</code>"; |
| } |
| } |
| } |
| |
| document.getElementById('imageInput').addEventListener('change', async function (e) { |
| if (!session) { |
| await init(); |
| } |
| |
| const file = e.target.files[0]; |
| if (!file) return; |
| |
| |
| if (!file.type.startsWith('image/')) { |
| alert('Please select a valid image file.'); |
| return; |
| } |
| |
| const image = new Image(); |
| const objectUrl = URL.createObjectURL(file); |
| image.src = objectUrl; |
| |
| image.onload = async function () { |
| document.getElementById('status').innerText = "Processing..."; |
| |
| |
| let canvas = document.createElement("canvas"); |
| const size = 256; |
| canvas.width = size; |
| canvas.height = size; |
| let ctx = canvas.getContext("2d"); |
| ctx.drawImage(image, 0, 0, size, size); |
| |
| const input_img = ctx.getImageData(0, 0, size, size); |
| const test = preprocess(input_img, size, size); |
| const input = new ort.Tensor(new Float32Array(test), [1, 3, size, size]); |
| |
| try { |
| const result = await session.run({ "input": input }); |
| |
| const output = result["output"] || result["out"] || Object.values(result)[0]; |
| |
| if (!output) throw new Error("No output tensor found in model result"); |
| |
| const imgdata = postprocess(output); |
| |
| |
| const outCanvas = document.getElementById('outputCanvas'); |
| outCanvas.width = image.width; |
| outCanvas.height = image.height; |
| const outCtx = outCanvas.getContext('2d'); |
| |
| |
| const tempCanvas = document.createElement('canvas'); |
| tempCanvas.width = size; |
| tempCanvas.height = size; |
| tempCanvas.getContext('2d').putImageData(imgdata, 0, 0); |
| |
| |
| outCtx.drawImage(tempCanvas, 0, 0, image.width, image.height); |
| |
| document.getElementById('status').innerText = "Done!"; |
| } catch (err) { |
| document.getElementById('status').innerText = "Error processing: " + err.message; |
| console.error(err); |
| } finally { |
| |
| URL.revokeObjectURL(objectUrl); |
| } |
| }; |
| }); |
| |
| |
| init(); |
| </script> |
| </body> |
|
|
| </html> |